All Files (51.71% covered at 12.63 hits/line)
256 files in total.
11637 relevant lines.
6018 lines covered and
5619 lines missed
-
1
require 'addressable/uri'
-
1
require 'armory/arguments.rb'
-
1
require 'armory/armory.rb'
-
1
require 'armory/auction.rb'
-
1
require 'armory/base.rb'
-
1
require 'armory/basic_realm.rb'
-
1
require 'armory/client.rb'
-
1
require 'armory/creatable.rb'
-
1
require 'armory/entities.rb'
-
1
require 'armory/enumerable.rb'
-
1
require 'armory/error.rb'
-
1
require 'armory/modifiable.rb'
-
1
require 'armory/null_object.rb'
-
1
require 'armory/realm.rb'
-
1
require 'armory/realm_status.rb'
-
1
require 'armory/rest/client'
-
1
require 'armory/rest/realmstatus.rb'
-
1
require 'armory/rest/auction.rb'
-
1
require 'armory/rest/auction_file.rb'
-
1
require 'armory/request.rb'
-
1
require 'armory/utils.rb'
-
1
require 'armory/version.rb'
-
1
require 'armory/world_zone.rb'
-
1
require 'armory/entity/uri'
-
1
module Armory
-
1
class Arguments < Array
-
# @return [Hash]
-
1
attr_reader :options
-
-
# Initializes a new Arguments object
-
#
-
# @return [Armory::Arguments]
-
1
def initialize(args)
-
@options = args.last.is_a?(::Hash) ? args.pop : {}
-
super(args.flatten)
-
end
-
end
-
end
-
1
require 'addressable/uri'
-
1
require 'armory/realm_status'
-
1
require 'armory/entity/hashtag'
-
1
require 'armory/entity/symbol'
-
1
require 'armory/entity/uri'
-
1
require 'armory/rest/client'
-
1
require 'armory/base'
-
1
require 'armory/modifiable'
-
-
1
module Armory
-
1
class Auction < Armory::Base
-
1
include Armory::Modifiable
-
-
# @return [URL]
-
1
url_attr_reader :url
-
# @return [Boolean]
-
-
1
def initialize(region, attrs = {})
-
5
super(region, attrs.fetch(:files) {|| [] }.first)
-
end
-
-
-
end
-
end
-
1
require 'armory/error'
-
1
require 'armory/meta_methods'
-
-
1
module Armory
-
1
class Base < Armory::MetaMethods
-
1
ruby_attr_reader :region
-
-
# Initializes a new object
-
#
-
# @param attrs [region:String, Hash]
-
# @return [Armory::Base]
-
1
def initialize(region = nil, attrs = {})
-
55
raise(Armory::Error::RegionMissing) if region.nil?
-
54
@region = region
-
54
super(attrs)
-
end
-
-
end
-
end
-
-
1
require 'equalizer'
-
1
require 'armory/base'
-
-
1
module Armory
-
1
class BasicRealm < Armory::Base
-
1
include Equalizer.new(:region, :slug)
-
# @return [String]
-
1
attr_reader :slug
-
-
-
end
-
end
-
1
require 'armory/error'
-
1
require 'armory/utils'
-
1
require 'armory/version'
-
-
1
module Armory
-
1
class Client
-
1
include Armory::Utils
-
1
attr_accessor :api_key, :proxy
-
1
attr_writer :user_agent
-
-
# Initializes a new Client object
-
#
-
# @param options [Hash]
-
# @return [Armory::Client]
-
1
def initialize(options = {})
-
37
options.each do |key, value|
-
33
instance_variable_set("@#{key}", value)
-
end
-
37
yield(self) if block_given?
-
37
validate_credential_type!
-
end
-
-
# @return [Boolean]
-
1
def api_key?
-
!!api_key
-
end
-
-
# @return [String]
-
1
def user_agent
-
18
@user_agent ||= "Armory Ruby Gem #{Armory::Version}"
-
end
-
-
# @return [Hash]
-
1
def credentials
-
{
-
:api_key => api_key,
-
41
}
-
end
-
-
# @return [Boolean]
-
1
def credentials?
-
4
credentials.values.all?
-
end
-
-
1
private
-
-
# Ensures that all credentials set during configuration are of a
-
# valid type. Valid types are String.
-
#
-
# @raise [Armory::Error::ConfigurationError] Error is raised when
-
# supplied Armory credentials are not a String.
-
1
def validate_credential_type!
-
37
credentials.each do |credential, value|
-
37
next if value.nil? || value.is_a?(String)
-
1
fail(Armory::Error::ConfigurationError.new("Invalid #{credential} specified: #{value.inspect} must be a string."))
-
end
-
end
-
-
1
def request_params(params = {})
-
15
params[:apikey] = api_key
-
15
params
-
end
-
-
-
end
-
end
-
1
require 'time'
-
1
require 'memoizable'
-
-
1
module Armory
-
1
module Creatable
-
1
include Memoizable
-
-
# Time when the object was created on Armory
-
#
-
# @return [Time]
-
1
def created_at
-
Time.parse(@headers["Last-Modified"]) unless @headers["Last-Modified"].nil?
-
end
-
1
memoize :created_at
-
1
alias_method :last_modified, :created_at
-
-
# @return [Boolean]
-
1
def created?
-
!!@attrs[:created_at]
-
end
-
1
memoize :created?
-
end
-
end
-
1
require 'memoizable'
-
1
require 'armory/entity/hashtag'
-
1
require 'armory/entity/symbol'
-
1
require 'armory/entity/uri'
-
-
1
module Armory
-
1
module Entities
-
1
include Memoizable
-
-
# @return [Boolean]
-
1
def entities?
-
!@attrs[:entities].nil? && @attrs[:entities].any? { |_, array| array.any? }
-
end
-
1
memoize :entities?
-
-
# @note Must include entities in your request for this method to work
-
# @return [Array<Armory::Entity::Hashtag>]
-
1
def hashtags
-
entities(Entity::Hashtag, :hashtags)
-
end
-
1
memoize :hashtags
-
-
# @return [Boolean]
-
1
def hashtags?
-
hashtags.any?
-
end
-
1
memoize :hashtags?
-
-
# @note Must include entities in your request for this method to work
-
# @return [Array<Armory::Entity::Symbol>]
-
1
def symbols
-
entities(Entity::Symbol, :symbols)
-
end
-
1
memoize :symbols
-
-
# @return [Boolean]
-
1
def symbols?
-
symbols.any?
-
end
-
1
memoize :symbols?
-
-
# @note Must include entities in your request for this method to work
-
# @return [Array<Armory::Entity::URI>]
-
1
def uris
-
entities(Entity::URI, :urls)
-
end
-
1
memoize :uris
-
1
alias_method :urls, :uris
-
-
# @return [Boolean]
-
1
def uris?
-
uris.any?
-
end
-
1
alias_method :urls?, :uris?
-
-
1
private
-
-
# @param klass [Class]
-
# @param key2 [Symbol]
-
# @param key1 [Symbol]
-
1
def entities(klass, key2, key1 = :entities)
-
@attrs.fetch(key1.to_sym, {}).fetch(key2.to_sym, []).collect do |entity|
-
klass.new(entity)
-
end
-
end
-
end
-
end
-
1
module Armory
-
1
class Hashtag < Armory::MetaMethods
-
# @return [String]
-
1
attr_reader :text
-
end
-
end
-
1
require 'armory/meta_methods'
-
-
1
module Armory
-
1
class Symbol < Armory::MetaMethods
-
# @return [String]
-
1
attr_reader :text
-
end
-
end
-
1
require 'armory/meta_methods'
-
-
1
module Armory
-
1
class URL < MetaMethods
-
-
1
url_attr_reader :url
-
-
end
-
-
end
-
1
module Armory
-
1
module Enumerable
-
1
include ::Enumerable
-
-
# @return [Enumerator]
-
1
def each(start = 0)
-
4
return to_enum(:each, start) unless block_given?
-
4
Array(@collection[start..-1]).each do |element|
-
9
yield(element)
-
end
-
2
unless last?
-
start = [@collection.size, start].max
-
fetch_next_page
-
each(start, &Proc.new)
-
end
-
2
self
-
end
-
-
1
private
-
-
# @return [Boolean]
-
1
def last?
-
2
true
-
end
-
end
-
end
-
1
module Armory
-
# Custom error class for rescuing from all Armory errors
-
1
class Error < StandardError
-
# @return [Integer]
-
1
attr_reader :code
-
-
1
class << self
-
# Create a new error from an HTTP response
-
#
-
# @param response [Faraday::Response]
-
# @return [Armory::Error]
-
1
def from_response(response)
-
10
message, code = parse_error(response.body)
-
10
new(message, code) # investigate adding response.response_headers here to handle errors
-
end
-
-
# @return [Hash]
-
1
def errors
-
# Also look at X-Mashery-Error-Code
-
@errors ||= {
-
# 400 => Armory::Error::BadRequest,
-
# 401 => Armory::Error::Unauthorized,
-
403 => Armory::Error::Forbidden,
-
404 => Armory::Error::NotFound,
-
# 406 => Armory::Error::NotAcceptable,
-
408 => Armory::Error::RequestTimeout,
-
# 420 => Armory::Error::EnhanceYourCalm,
-
# 422 => Armory::Error::UnprocessableEntity,
-
# 429 => Armory::Error::TooManyRequests,
-
500 => Armory::Error::InternalServerError,
-
502 => Armory::Error::BadGateway,
-
503 => Armory::Error::RateLimited,
-
504 => Armory::Error::GatewayTimeout,
-
16
}
-
end
-
-
1
def forbidden_messages
-
@forbidden_messages ||= {
-
'maintenance' => Armory::Error::Maintenance,
-
'Account Inactive' => Armory::Error::IncorrectAPIKey,
-
1
}
-
end
-
-
1
private
-
-
1
def parse_error(body)
-
10
if body.nil?
-
3
['', nil]
-
7
elsif body[:detail]
-
[body[:detail], nil]
-
end
-
end
-
-
end
-
-
# Initializes a new Error object
-
#
-
# @param message [Exception, String]
-
# @param rate_limit [Hash]
-
# @param code [Integer]
-
# @return [Armory::Error]
-
1
def initialize(message = '', code = nil)
-
35
super(message)
-
35
@code = code
-
end
-
-
1
ConfigurationError = Class.new(::ArgumentError)
-
-
-
# Raised when Armory returns a 4xx HTTP status code
-
1
ClientError = Class.new(self)
-
-
# Raised when Armory::Base does not receive a region
-
1
RegionMissing = Class.new(ClientError)
-
1
InvalidRealm = Class.new(ClientError)
-
-
# Raised when Armory returns the HTTP status code 403
-
1
Forbidden = Class.new(ClientError)
-
1
IncorrectAPIKey = Class.new(Forbidden)
-
-
# Raised when Armory returns the HTTP status code 404
-
1
NotFound = Class.new(ClientError)
-
-
# Raised when Armory returns the HTTP status code 408
-
1
RequestTimeout = Class.new(ClientError)
-
-
# Raised when Armory returns a 5xx HTTP status code
-
1
ServerError = Class.new(self)
-
-
1
Maintenance = Class.new(ServerError)
-
-
# Raised when Armory returns the HTTP status code 500
-
1
InternalServerError = Class.new(ServerError)
-
-
# Raised when Armory returns the HTTP status code 502
-
1
BadGateway = Class.new(ServerError)
-
-
# Raised when Armory returns the HTTP status code 503
-
1
RateLimited = Class.new(ServerError)
-
-
# Raised when Armory returns the HTTP status code 504
-
1
GatewayTimeout = Class.new(ServerError)
-
end
-
end
-
1
require 'memoizable'
-
-
1
module Armory
-
1
class MetaMethods
-
1
extend Forwardable
-
1
include Memoizable
-
1
include Armory::Utils
-
-
# @return [Hash]
-
1
attr_reader :attrs
-
1
alias_method :to_h, :attrs
-
1
alias_method :to_hash, :to_h
-
-
-
1
class << self
-
# Keep ruby's original accessible as we use it for region
-
1
alias_method :ruby_attr_reader, :attr_reader
-
-
# Define methods that retrieve the value from attributes
-
#
-
# @param attrs [Array, Symbol]
-
1
def attr_reader(*attrs)
-
6
attrs.each do |attr|
-
13
define_attribute_method(attr)
-
13
define_predicate_method(attr)
-
end
-
end
-
-
1
def predicate_attr_reader(*attrs)
-
1
attrs.each do |attr|
-
2
define_predicate_method(attr)
-
2
deprecate_attribute_method(attr)
-
end
-
end
-
-
# Define object methods from attributes
-
#
-
# @param klass [Symbol]
-
# @param key1 [Symbol]
-
# @param key2 [Symbol]
-
1
def object_attr_reader(klass, key1, key_alias = key1)
-
2
define_attribute_method(key1, klass, key_alias)
-
2
define_predicate_method(key1, key_alias)
-
end
-
-
# Define URI methods from attributes
-
# Defines both uri and url versions (:display_uri also defines display_url)
-
# @param attrs [Array, Symbol]
-
1
def url_attr_reader(*attrs)
-
2
attrs.each do |primary_key|
-
2
define_uri_method(primary_key)
-
2
define_predicate_method(primary_key)
-
end
-
# attrs.each do |primary_key|
-
# primary = 'url'; secondary = 'uri'
-
# array = primary_key.to_s.split('_')
-
# index = array.index(primary)
-
# if index.nil? # No url present, so alias uri to url instead
-
# primary, secondary = secondary, primary
-
# index = array.index(primary)
-
# end
-
# array[index] = secondary
-
# secondary_key = array.join('_').to_sym
-
# define_uri_method(primary_key)
-
# alias_method(secondary_key, primary_key)
-
# define_predicate_method(secondary_key, primary_key)
-
# alias_method(:"#{secondary_key}?", :"#{primary_key}?")
-
# end
-
end
-
-
# Dynamically define a method for a URI
-
#
-
# @param key1 [Symbol]
-
1
def define_uri_method(key1)
-
2
define_method(key1) do ||
-
5
Addressable::URI.parse(@attrs[key1]) unless @attrs[key1].nil?
-
end
-
2
memoize(key1)
-
end
-
-
# Dynamically define a method for an attribute
-
#
-
# @param key1 [Symbol]
-
# @param klass [Symbol]
-
# @param key_alias [Symbol] - alias
-
1
def define_attribute_method(key1, klass = nil, key_alias = key1)
-
15
define_method(key_alias) do ||
-
28
if @attrs[key1].nil? || @attrs[key1].respond_to?(:empty?) && @attrs[key1].empty?
-
4
NullObject.new
-
else
-
24
if klass.nil?
-
22
@attrs[key1]
-
else
-
2
Armory.const_get(klass).new(@attrs[key1])
-
end
-
end
-
end
-
15
memoize(key_alias)
-
end
-
-
# @param key [Symbol]
-
1
def deprecate_attribute_method(key, key_alias = key)
-
2
define_method(key_alias) do ||
-
warn "#{Kernel.caller.first}: [DEPRECATION] ##{key} is deprecated. Use ##{key}? instead."
-
@attrs[key]
-
end
-
2
memoize(key_alias)
-
end
-
-
# Dynamically define a predicate method for an attribute
-
#
-
# @param key1 [Symbol]
-
# @param key_alias [Symbol]
-
1
def define_predicate_method(key1, key_alias = key1)
-
19
define_method(:"#{key_alias}?") do ||
-
4
!@attrs[key1].nil? && @attrs[key1] != false && !(@attrs[key1].respond_to?(:empty?) && @attrs[key1].empty?)
-
end
-
19
memoize(:"#{key_alias}?")
-
end
-
-
end
-
-
# Initializes a new object
-
#
-
# @param attrs [region:String, Hash]
-
# @return [Armory::Base]
-
1
def initialize(attrs = {})
-
60
@attrs = attrs || {}
-
end
-
-
-
end
-
end
-
1
require 'memoizable'
-
-
1
module Armory
-
1
module Modifiable
-
1
include Memoizable
-
-
# Date when the object was last modified on the Armory
-
#
-
# @return [Time]
-
1
def last_modified
-
2
Time.at(@attrs[:lastModified]/1000) unless @attrs[:lastModified].nil?
-
end
-
1
memoize :last_modified
-
1
alias_method :lastmodified, :last_modified
-
-
# @return [Boolean]
-
1
def last_modified?
-
!!@attrs[:lastModified]
-
end
-
1
memoize :last_modified?
-
-
1
alias_method :lastmodified?, :last_modified?
-
end
-
end
-
1
require 'naught'
-
-
1
module Armory
-
1
NullObject = Naught.build do |config|
-
1
config.black_hole
-
1
config.define_explicit_conversions
-
1
config.define_implicit_conversions
-
1
config.predicates_return false
-
-
1
def nil?
-
2
true
-
end
-
end
-
end
-
1
require 'armory/basic_realm'
-
1
require 'armory/world_zone'
-
-
1
module Armory
-
1
class Realm < Armory::BasicRealm
-
-
# @return [String]
-
1
attr_reader :name, :battlegroup, :locale, :timezone, :type, :population
-
# @return [Boolean]
-
1
predicate_attr_reader :queue, :status
-
-
1
object_attr_reader :WorldZone, :wintergrasp
-
1
object_attr_reader :WorldZone, :'tol-barad', :tol_barad
-
1
alias_method :tolbarad, :tol_barad
-
-
# @return [Array<Armory::BasicRealm>]
-
1
def connected_realms
-
2
@attrs.fetch(:connected_realms, []).collect do |realmslug|
-
3
BasicRealm.new(@region, slug: realmslug)
-
end
-
end
-
1
memoize :connected_realms
-
1
alias_method :connected, :connected_realms
-
1
alias_method :connectedrealms, :connected_realms
-
end
-
end
-
1
require 'memoizable'
-
1
require 'armory/creatable'
-
1
require 'armory/null_object'
-
1
require 'armory/utils'
-
1
require 'armory/base'
-
1
require 'armory/realm'
-
1
require 'armory/enumerable'
-
-
1
module Armory
-
1
class RealmStatus < Base
-
1
include Armory::Creatable
-
1
include Armory::Enumerable
-
1
include Armory::Utils
-
1
include Memoizable
-
-
# @return [Hash]
-
1
attr_reader :attrs
-
1
alias_method :to_h, :attrs
-
1
alias_method :to_hash, :to_h
-
-
1
def realms
-
3
@collection
-
end
-
-
# Initializes a new RealmStatus object
-
#
-
# @param attrs [Hash]
-
# @return [Armory::RealmStatus]
-
1
def initialize(region, attrs = {})
-
7
super
-
7
@collection = @attrs.fetch(:realms, []).collect do |realm|
-
18
Realm.new(@region, realm)
-
end
-
end
-
-
1
private
-
-
# @param klass [Class]
-
# @param key1 [Symbol]
-
1
def sub_collection(klass, key1)
-
@attrs.fetch(key1.to_sym, []).collect do |item|
-
klass.new(@region, item)
-
end
-
end
-
end
-
end
-
1
require 'armory/rest/client'
-
-
1
module Armory
-
1
class Request
-
1
attr_accessor :client, :request_method, :path, :options
-
1
alias_method :verb, :request_method
-
-
1
def region=(newregion)
-
15
@region = newregion.upcase
-
end
-
-
1
def region
-
35
@region ||= "US"
-
end
-
-
1
def locale=(newlocale)
-
@locale = newlocale
-
end
-
-
1
def locale
-
15
@locale ||= "en_US"
-
end
-
-
-
-
-
-
# @param client [Armory::Client]
-
# @param request_method [String, Symbol]
-
# @param path [String]
-
# @param options [Hash]
-
# @return [Armory::Request]
-
1
def initialize(client, request_method, path, options = {})
-
15
@client = client
-
15
@request_method = request_method.to_sym
-
15
@path = include_region_in_path(path, options)
-
15
@options = include_locale_in_options(options)
-
end
-
-
# @return [Hash]
-
1
def perform
-
15
@client.send(@request_method, @path, @options).body
-
end
-
-
# @param klass [Class]
-
# @param request [Armory::Request]
-
# @return [Object]
-
1
def perform_with_object(klass)
-
15
klass.new(region, perform)
-
end
-
-
# @param klass [Class]
-
# @return [Array]
-
1
def perform_with_objects(klass)
-
perform.collect do |element|
-
klass.new(region, element)
-
end
-
end
-
-
1
private
-
-
1
def include_region_in_path(path, options = {})
-
15
self.region = options.delete(:region) || self.region
-
15
::URI::join(Armory::REST::Client::PROTOCOL+region+Armory::REST::Client::BATTLENET,path)
-
end
-
-
1
def include_locale_in_options(options = {})
-
30
options.fetch(:locale) {|k| options[k] = locale }
-
15
options
-
end
-
end
-
end
-
1
require 'armory/rest/realmstatus'
-
1
require 'armory/rest/auction'
-
1
require 'armory/rest/auction_file'
-
# require 'armory/rest/other-api-endpoints'
-
-
1
module Armory
-
1
module REST
-
1
module API
-
1
include Armory::REST::RealmStatus
-
1
include Armory::REST::Auction
-
1
include Armory::REST::AuctionFile
-
end
-
end
-
end
-
1
require 'armory/arguments'
-
1
require 'armory/auction'
-
1
require 'armory/request'
-
1
require 'armory/rest/utils'
-
1
require 'armory/utils'
-
-
1
module Armory
-
1
module REST
-
1
module Auction
-
1
include Armory::REST::Utils
-
1
include Armory::Utils
-
-
# Auction APIs currently provide rolling batches of data about current auctions.
-
# Fetching auction dumps is a two step process that involves checking a per-realm index file to
-
# determine if a recent dump has been generated and then fetching the most recently generated dump
-
# file if necessary.
-
# This API resource provides a per-realm list of recently generated auction house data dumps.
-
#
-
# @return [Armory::Auction] Auction file location
-
# @param options [Hash] A customizable set of options.
-
# - realms : list of realms to limit to
-
1
def auction(realm_slug, options = {})
-
3
perform_with_object(:get, "/wow/auction/data/#{extract_slug(realm_slug)}", options, Armory::Auction)
-
end
-
-
end
-
end
-
end
-
1
require 'armory/arguments'
-
1
require 'armory/auction'
-
1
require 'armory/request'
-
1
require 'armory/rest/utils'
-
1
require 'armory/utils'
-
-
1
module Armory
-
1
module REST
-
1
module AuctionFile
-
1
include Armory::REST::Utils
-
1
include Armory::Utils
-
-
# Downloads the requested auction file
-
#
-
# @return [Armory::Auction] Auction file location
-
# @param options [Hash] A customizable set of options.
-
# - realms : list of realms to limit to
-
1
def auction_file(auction_url, options = {})
-
perform_with_object(:get, "/wow/auction/data/#{extract_auction_file_url(auction_url)}", options, Armory::Auction)
-
end
-
-
end
-
end
-
end
-
1
require 'base64'
-
1
require 'faraday'
-
1
require 'json'
-
1
require 'timeout'
-
1
require 'armory/client'
-
1
require 'armory/error'
-
1
require 'armory/rest/api'
-
1
require 'armory/rest/response/parse_json'
-
1
require 'armory/rest/response/raise_error'
-
#require 'dalli'
-
-
## HACK: Monkey-patch middleware to return full URL rather than just URI part for caching
-
# module FaradayMiddleware
-
# # Public: Caches GET responses and pulls subsequent ones from the cache.
-
# class Caching < Faraday::Middleware
-
# def cache_key(env)
-
# url = env[:url].dup
-
# if url.query && params_to_ignore.any?
-
# params = parse_query url.query
-
# params.reject! {|k,| params_to_ignore.include? k }
-
# url.query = params.any? ? build_query(params) : nil
-
# end
-
# url.normalize!
-
# url.to_s
-
# end
-
# end
-
# end
-
-
1
module Armory
-
1
module REST
-
# Wrapper for the Armory REST API
-
1
class Client < Armory::Client
-
1
include Armory::REST::API
-
1
PROTOCOL = 'https://'
-
1
BATTLENET = '.api.battle.net'
-
1
URL_PREFIX = PROTOCOL+'us'+BATTLENET
-
1
ENDPOINT = URL_PREFIX
-
-
# @return [Hash]
-
1
def connection_options
-
@connection_options ||= {
-
:builder => middleware,
-
:headers => {
-
:accept => 'application/json',
-
:user_agent => user_agent,
-
},
-
:request => {
-
:open_timeout => 10,
-
:timeout => 30,
-
},
-
:proxy => proxy,
-
19
}
-
end
-
-
# @note Faraday's middleware stack implementation is comparable to that of Rack middleware. The order of middleware is important: the first middleware on the list wraps all others, while the last middleware is the innermost one.
-
# @see https://github.com/technoweenie/faraday#advanced-middleware-usage
-
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
-
# @return [Faraday::RackBuilder]
-
1
def middleware
-
@middleware ||= Faraday::RackBuilder.new do |faraday|
-
-
-
# Encodes as "application/x-www-form-urlencoded" if not already encoded
-
18
faraday.request :url_encoded
-
# Handle error responses
-
18
faraday.response :armory_raise_error
-
# Parse JSON response bodies
-
18
faraday.response :armory_parse_json
-
-
# faraday.response :caching, Dalli::Client.new('localhost:11211', { :namespace => "armory", :compress => true, :expires_in => 60*60 }), :ignore_params => %w[access_token]
-
-
18
faraday.response :logger
-
# Set default HTTP adapter
-
18
faraday.adapter :net_http
-
18
end
-
end
-
-
# Perform an HTTP GET request
-
1
def get(path, params = {})
-
15
headers = request_headers
-
15
params = request_params(params)
-
15
request(:get, path, params, headers)
-
end
-
-
# Perform an HTTP POST request
-
1
def post(path, params = {})
-
headers = request_headers
-
params = request_params(params)
-
request(:post, path, params, headers)
-
end
-
-
1
private
-
-
# Returns a Faraday::Connection object
-
#
-
# @return [Faraday::Connection]
-
1
def connection
-
18
@connection ||= Faraday.new(URL_PREFIX, connection_options)
-
end
-
-
1
def request(method, path, params = {}, headers = {})
-
34
connection.send(method.to_sym, path, params) { |request| request.headers.update(headers) }.env
-
rescue Faraday::Error::TimeoutError, Timeout::Error => error
-
2
raise(Armory::Error::RequestTimeout.new(error))
-
rescue Faraday::Error::ClientError, JSON::ParserError => error
-
2
raise(Armory::Error.new(error))
-
end
-
-
1
def request_headers
-
15
headers = {}
-
end
-
-
end
-
-
end
-
end
-
1
require 'armory/arguments'
-
1
require 'armory/realm_status'
-
1
require 'armory/request'
-
1
require 'armory/rest/utils'
-
1
require 'armory/utils'
-
-
1
module Armory
-
1
module REST
-
1
module RealmStatus
-
1
include Armory::REST::Utils
-
1
include Armory::Utils
-
-
# The realm status API allows developers to retrieve realm status information.
-
# This information is limited to whether or not the realm is up, the type and state of the realm,
-
# the current population, and the status of the two world PvP zones.
-
#
-
# @return [Array<Armory::RealmStatus>] Realm statuses
-
# @param options [Hash] A customizable set of options.
-
# - realms : list of realms to limit to
-
1
def realm_status(options = {})
-
12
perform_with_object(:get, '/wow/realm/status', options, Armory::RealmStatus)
-
end
-
-
1
alias_method :status, :realm_status
-
end
-
end
-
end
-
1
require 'faraday'
-
1
require 'json'
-
-
1
module Armory
-
1
module REST
-
1
module Response
-
1
class ParseJson < Faraday::Response::Middleware
-
1
WHITESPACE_REGEX = /\A^\s*$\z/
-
-
1
def parse(body)
-
15
case body
-
when WHITESPACE_REGEX, nil
-
nil
-
else
-
12
JSON.parse(body, :symbolize_names => true)
-
end
-
end
-
-
1
def on_complete(response)
-
15
response.body = parse(response.body) if respond_to?(:parse) && !unparsable_status_codes.include?(response.status)
-
end
-
-
1
def unparsable_status_codes
-
15
[204, 301, 302, 304]
-
end
-
end
-
end
-
end
-
end
-
-
1
Faraday::Response.register_middleware :armory_parse_json => Armory::REST::Response::ParseJson
-
1
require 'faraday'
-
1
require 'armory/error'
-
-
1
module Armory
-
1
module REST
-
1
module Response
-
1
class RaiseError < Faraday::Response::Middleware
-
1
def on_complete(response)
-
15
status_code = response.status.to_i
-
15
klass = Armory::Error.errors[status_code]
-
15
return unless klass
-
10
if klass == Armory::Error::Forbidden
-
1
fail(handle_forbidden_errors(response))
-
else
-
9
fail(klass.from_response(response))
-
end
-
end
-
-
1
private
-
-
1
def handle_forbidden_errors(response)
-
1
error = Armory::Error::Forbidden.from_response(response)
-
1
klass = Armory::Error.forbidden_messages[error.message]
-
1
if klass
-
klass.from_response(response)
-
else
-
1
error
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
Faraday::Response.register_middleware :armory_raise_error => Armory::REST::Response::RaiseError
-
1
require 'addressable/uri'
-
1
require 'armory/arguments'
-
1
require 'armory/request'
-
1
require 'armory/utils'
-
1
require 'armory/basic_realm'
-
-
1
module Armory
-
1
module REST
-
1
module Utils
-
1
include Armory::Utils
-
1
URI_SUBSTRING = '://'
-
1
DEFAULT_CURSOR = -1
-
-
# Take a realm slug, or Armory::BasicRealm object and return its slug
-
#
-
# @param object [String, Armory::BasicRealm] A slug or object.
-
# @return [String]
-
1
def extract_slug(object)
-
7
case object
-
3
when ::String; object
-
3
when Armory::BasicRealm; object.slug
-
1
else raise (Armory::Error::InvalidRealm)
-
end
-
end
-
-
# Take an auction url string, URL, or Armory::Auction object and return the URL as a string
-
#
-
# @param object [String, Armory::BasicRealm] A slug or object.
-
# @return [String]
-
1
def extract_auction_file_url(object)
-
3
case object
-
1
when Addressable::URI; object.to_s
-
1
when ::String; Addressable::URI.parse(object).to_s
-
1
when Armory::Auction; object.url.to_s
-
end
-
end
-
-
1
private
-
-
# # Take a URI string or Armory::Identity object and return its ID
-
# #
-
# # @param object [Integer, String, URI, Armory::Identity] An ID, URI, or object.
-
# # @return [Integer]
-
# def extract_id(object)
-
# case object
-
# when ::Integer
-
# object
-
# when ::String
-
# object.split('/').last.to_i
-
# when URI
-
# object.path.split('/').last.to_i
-
# when Armory::Identity
-
# object.id
-
# end
-
# end
-
-
# @param request_method [Symbol]
-
# @param path [String]
-
# @param options [Hash]
-
# @param klass [Class]
-
1
def perform_with_object(request_method, path, options, klass)
-
15
request = Armory::Request.new(self, request_method, path, options)
-
15
request.perform_with_object(klass)
-
end
-
-
# @param request_method [Symbol]
-
# @param path [String]
-
# @param options [Hash]
-
# @param klass [Class]
-
1
def perform_with_objects(request_method, path, options, klass)
-
request = Armory::Request.new(self, request_method, path, options)
-
request.perform_with_objects(klass)
-
end
-
-
# @param klass [Class]
-
# @param request_method [Symbol]
-
# @param path [String]
-
# @param args [Array]
-
# @return [Array]
-
1
def parallel_objects_from_response(klass, request_method, path, args)
-
arguments = Armory::Arguments.new(args)
-
pmap(arguments) do |object|
-
perform_with_object(request_method, path, arguments.options.merge(:id => extract_id(object)), klass)
-
end
-
end
-
-
end
-
end
-
end
-
1
module Armory
-
1
module Utils
-
-
# Returns a new array with the concatenated results of running block once for every element in enumerable.
-
# If no block is given, an enumerator is returned instead.
-
#
-
# @param enumerable [Enumerable]
-
# @return [Array, Enumerator]
-
1
def flat_pmap(enumerable)
-
1
return to_enum(:flat_pmap, enumerable) unless block_given?
-
1
pmap(enumerable, &Proc.new).flatten!(1)
-
end
-
1
module_function :flat_pmap
-
-
# Returns a new array with the results of running block once for every element in enumerable.
-
# If no block is given, an enumerator is returned instead.
-
#
-
# @param enumerable [Enumerable]
-
# @return [Array, Enumerator]
-
1
def pmap(enumerable)
-
2
return to_enum(:pmap, enumerable) unless block_given?
-
2
if enumerable.count == 1
-
enumerable.collect { |object| yield(object) }
-
else
-
42
enumerable.collect { |object| Thread.new { yield(object) } }.collect(&:value)
-
end
-
end
-
1
module_function :pmap
-
end
-
end
-
1
require 'armory/meta_methods'
-
-
1
module Armory
-
1
class WorldZone < Armory::MetaMethods
-
-
# :wintergrasp=>{
-
# :area=>1,
-
# :"controlling-faction"=>0,
-
# :status=>0,
-
# :next=>1414569632644},
-
# :"tol-barad"=>{
-
# :area=>21,
-
# :"controlling-faction"=>0,
-
# :status=>0,
-
# :next=>1414568667783},
-
-
# @return [Integer]
-
1
attr_reader :area, :status, :next
-
-
-
1
def controllingfaction
-
1
@attrs[:'controlling-faction']
-
end
-
1
alias_method :controlling_faction, :controllingfaction
-
-
end
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2013 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
begin
-
1
require "addressable/idna/native"
-
rescue LoadError
-
# libidn or the idn gem was not available, fall back on a pure-Ruby
-
# implementation...
-
1
require "addressable/idna/pure"
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2013 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
require "idn"
-
-
module Addressable
-
module IDNA
-
def self.punycode_encode(value)
-
IDN::Punycode.encode(value)
-
end
-
-
def self.punycode_decode(value)
-
IDN::Punycode.decode(value)
-
end
-
-
def self.unicode_normalize_kc(value)
-
IDN::Stringprep.nfkc_normalize(value)
-
end
-
-
def self.to_ascii(value)
-
IDN::Idna.toASCII(value)
-
end
-
-
def self.to_unicode(value)
-
IDN::Idna.toUnicode(value)
-
end
-
end
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2013 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
module Addressable
-
1
module IDNA
-
# This module is loosely based on idn_actionmailer by Mick Staugaard,
-
# the unicode library by Yoshida Masato, and the punycode implementation
-
# by Kazuhiro Nishiyama. Most of the code was copied verbatim, but
-
# some reformatting was done, and some translation from C was done.
-
#
-
# Without their code to work from as a base, we'd all still be relying
-
# on the presence of libidn. Which nobody ever seems to have installed.
-
#
-
# Original sources:
-
# http://github.com/staugaard/idn_actionmailer
-
# http://www.yoshidam.net/Ruby.html#unicode
-
# http://rubyforge.org/frs/?group_id=2550
-
-
-
1
UNICODE_TABLE = File.expand_path(
-
File.join(File.dirname(__FILE__), '../../..', 'data/unicode.data')
-
)
-
-
1
ACE_PREFIX = "xn--"
-
-
1
UTF8_REGEX = /\A(?:
-
[\x09\x0A\x0D\x20-\x7E] # ASCII
-
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
-
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
-
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
-
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
-
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
-
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
-
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
-
)*\z/mnx
-
-
1
UTF8_REGEX_MULTIBYTE = /(?:
-
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
-
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
-
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
-
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
-
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
-
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5
-
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
-
)/mnx
-
-
# :startdoc:
-
-
# Converts from a Unicode internationalized domain name to an ASCII
-
# domain name as described in RFC 3490.
-
1
def self.to_ascii(input)
-
41
input = input.dup
-
41
if input.respond_to?(:force_encoding)
-
41
input.force_encoding(Encoding::ASCII_8BIT)
-
end
-
41
if input =~ UTF8_REGEX && input =~ UTF8_REGEX_MULTIBYTE
-
parts = unicode_downcase(input).split('.')
-
parts.map! do |part|
-
if part.respond_to?(:force_encoding)
-
part.force_encoding(Encoding::ASCII_8BIT)
-
end
-
if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE
-
ACE_PREFIX + punycode_encode(unicode_normalize_kc(part))
-
else
-
part
-
end
-
end
-
parts.join('.')
-
else
-
41
input
-
end
-
end
-
-
# Converts from an ASCII domain name to a Unicode internationalized
-
# domain name as described in RFC 3490.
-
1
def self.to_unicode(input)
-
parts = input.split('.')
-
parts.map! do |part|
-
if part =~ /^#{ACE_PREFIX}/
-
punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1])
-
else
-
part
-
end
-
end
-
output = parts.join('.')
-
if output.respond_to?(:force_encoding)
-
output.force_encoding(Encoding::UTF_8)
-
end
-
output
-
end
-
-
# Unicode normalization form KC.
-
1
def self.unicode_normalize_kc(input)
-
293
input = input.to_s unless input.is_a?(String)
-
293
unpacked = input.unpack("U*")
-
293
unpacked =
-
unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked)))
-
293
return unpacked.pack("U*")
-
end
-
-
##
-
# Unicode aware downcase method.
-
#
-
# @api private
-
# @param [String] input
-
# The input string.
-
# @return [String] The downcased result.
-
1
def self.unicode_downcase(input)
-
unpacked = input.unpack("U*")
-
unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) }
-
return unpacked.pack("U*")
-
end
-
2
(class <<self; private :unicode_downcase; end)
-
-
1
def self.unicode_compose(unpacked)
-
293
unpacked_result = []
-
293
length = unpacked.length
-
-
293
return unpacked if length == 0
-
-
252
starter = unpacked[0]
-
252
starter_cc = lookup_unicode_combining_class(starter)
-
252
starter_cc = 256 if starter_cc != 0
-
252
for i in 1...length
-
1583
ch = unpacked[i]
-
1583
cc = lookup_unicode_combining_class(ch)
-
-
if (starter_cc == 0 &&
-
1583
(composite = unicode_compose_pair(starter, ch)) != nil)
-
starter = composite
-
startercc = lookup_unicode_combining_class(composite)
-
else
-
1583
unpacked_result << starter
-
1583
starter = ch
-
1583
startercc = cc
-
end
-
end
-
252
unpacked_result << starter
-
252
return unpacked_result
-
end
-
2
(class <<self; private :unicode_compose; end)
-
-
1
def self.unicode_compose_pair(ch_one, ch_two)
-
1583
if ch_one >= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT &&
-
ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT
-
# Hangul L + V
-
return HANGUL_SBASE + (
-
(ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE)
-
) * HANGUL_TCOUNT
-
elsif ch_one >= HANGUL_SBASE &&
-
ch_one < HANGUL_SBASE + HANGUL_SCOUNT &&
-
(ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 &&
-
ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT
-
# Hangul LV + T
-
return ch_one + (ch_two - HANGUL_TBASE)
-
end
-
-
1583
p = []
-
1583
ucs4_to_utf8 = lambda do |ch|
-
# For some reason, rcov likes to drop BUS errors here.
-
3166
if ch < 128
-
3166
p << ch
-
elsif ch < 2048
-
p << (ch >> 6 | 192)
-
p << (ch & 63 | 128)
-
elsif ch < 0x10000
-
p << (ch >> 12 | 224)
-
p << (ch >> 6 & 63 | 128)
-
p << (ch & 63 | 128)
-
elsif ch < 0x200000
-
p << (ch >> 18 | 240)
-
p << (ch >> 12 & 63 | 128)
-
p << (ch >> 6 & 63 | 128)
-
p << (ch & 63 | 128)
-
elsif ch < 0x4000000
-
p << (ch >> 24 | 248)
-
p << (ch >> 18 & 63 | 128)
-
p << (ch >> 12 & 63 | 128)
-
p << (ch >> 6 & 63 | 128)
-
p << (ch & 63 | 128)
-
elsif ch < 0x80000000
-
p << (ch >> 30 | 252)
-
p << (ch >> 24 & 63 | 128)
-
p << (ch >> 18 & 63 | 128)
-
p << (ch >> 12 & 63 | 128)
-
p << (ch >> 6 & 63 | 128)
-
p << (ch & 63 | 128)
-
end
-
end
-
-
1583
ucs4_to_utf8.call(ch_one)
-
1583
ucs4_to_utf8.call(ch_two)
-
-
1583
return lookup_unicode_composition(p)
-
end
-
2
(class <<self; private :unicode_compose_pair; end)
-
-
1
def self.unicode_sort_canonical(unpacked)
-
293
unpacked = unpacked.dup
-
293
i = 1
-
293
length = unpacked.length
-
-
293
return unpacked if length < 2
-
-
252
while i < length
-
1583
last = unpacked[i-1]
-
1583
ch = unpacked[i]
-
1583
last_cc = lookup_unicode_combining_class(last)
-
1583
cc = lookup_unicode_combining_class(ch)
-
1583
if cc != 0 && last_cc != 0 && last_cc > cc
-
unpacked[i] = last
-
unpacked[i-1] = ch
-
i -= 1 if i > 1
-
else
-
1583
i += 1
-
end
-
end
-
252
return unpacked
-
end
-
2
(class <<self; private :unicode_sort_canonical; end)
-
-
1
def self.unicode_decompose(unpacked)
-
293
unpacked_result = []
-
293
for cp in unpacked
-
1835
if cp >= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT
-
l, v, t = unicode_decompose_hangul(cp)
-
unpacked_result << l
-
unpacked_result << v if v
-
unpacked_result << t if t
-
else
-
1835
dc = lookup_unicode_compatibility(cp)
-
1835
unless dc
-
1835
unpacked_result << cp
-
else
-
unpacked_result.concat(unicode_decompose(dc.unpack("U*")))
-
end
-
end
-
end
-
293
return unpacked_result
-
end
-
2
(class <<self; private :unicode_decompose; end)
-
-
1
def self.unicode_decompose_hangul(codepoint)
-
sindex = codepoint - HANGUL_SBASE;
-
if sindex < 0 || sindex >= HANGUL_SCOUNT
-
l = codepoint
-
v = t = nil
-
return l, v, t
-
end
-
l = HANGUL_LBASE + sindex / HANGUL_NCOUNT
-
v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
-
t = HANGUL_TBASE + sindex % HANGUL_TCOUNT
-
if t == HANGUL_TBASE
-
t = nil
-
end
-
return l, v, t
-
end
-
2
(class <<self; private :unicode_decompose_hangul; end)
-
-
1
def self.lookup_unicode_combining_class(codepoint)
-
5001
codepoint_data = UNICODE_DATA[codepoint]
-
5001
(codepoint_data ?
-
4431
(codepoint_data[UNICODE_DATA_COMBINING_CLASS] || 0) :
-
570
0)
-
end
-
2
(class <<self; private :lookup_unicode_combining_class; end)
-
-
1
def self.lookup_unicode_compatibility(codepoint)
-
1835
codepoint_data = UNICODE_DATA[codepoint]
-
1835
(codepoint_data ?
-
codepoint_data[UNICODE_DATA_COMPATIBILITY] : nil)
-
end
-
2
(class <<self; private :lookup_unicode_compatibility; end)
-
-
1
def self.lookup_unicode_lowercase(codepoint)
-
codepoint_data = UNICODE_DATA[codepoint]
-
(codepoint_data ?
-
(codepoint_data[UNICODE_DATA_LOWERCASE] || codepoint) :
-
codepoint)
-
end
-
2
(class <<self; private :lookup_unicode_lowercase; end)
-
-
1
def self.lookup_unicode_composition(unpacked)
-
1583
return COMPOSITION_TABLE[unpacked]
-
end
-
2
(class <<self; private :lookup_unicode_composition; end)
-
-
1
HANGUL_SBASE = 0xac00
-
1
HANGUL_LBASE = 0x1100
-
1
HANGUL_LCOUNT = 19
-
1
HANGUL_VBASE = 0x1161
-
1
HANGUL_VCOUNT = 21
-
1
HANGUL_TBASE = 0x11a7
-
1
HANGUL_TCOUNT = 28
-
1
HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT # 588
-
1
HANGUL_SCOUNT = HANGUL_LCOUNT * HANGUL_NCOUNT # 11172
-
-
1
UNICODE_DATA_COMBINING_CLASS = 0
-
1
UNICODE_DATA_EXCLUSION = 1
-
1
UNICODE_DATA_CANONICAL = 2
-
1
UNICODE_DATA_COMPATIBILITY = 3
-
1
UNICODE_DATA_UPPERCASE = 4
-
1
UNICODE_DATA_LOWERCASE = 5
-
1
UNICODE_DATA_TITLECASE = 6
-
-
1
begin
-
1
if defined?(FakeFS)
-
fakefs_state = FakeFS.activated?
-
FakeFS.deactivate!
-
end
-
# This is a sparse Unicode table. Codepoints without entries are
-
# assumed to have the value: [0, 0, nil, nil, nil, nil, nil]
-
1
UNICODE_DATA = File.open(UNICODE_TABLE, "rb") do |file|
-
1
Marshal.load(file.read)
-
end
-
ensure
-
1
if defined?(FakeFS)
-
FakeFS.activate! if fakefs_state
-
end
-
end
-
-
1
COMPOSITION_TABLE = {}
-
1
for codepoint, data in UNICODE_DATA
-
4233
canonical = data[UNICODE_DATA_CANONICAL]
-
4233
exclusion = data[UNICODE_DATA_EXCLUSION]
-
-
4233
if canonical && exclusion == 0
-
918
COMPOSITION_TABLE[canonical.unpack("C*")] = codepoint
-
end
-
end
-
-
1
UNICODE_MAX_LENGTH = 256
-
1
ACE_MAX_LENGTH = 256
-
-
1
PUNYCODE_BASE = 36
-
1
PUNYCODE_TMIN = 1
-
1
PUNYCODE_TMAX = 26
-
1
PUNYCODE_SKEW = 38
-
1
PUNYCODE_DAMP = 700
-
1
PUNYCODE_INITIAL_BIAS = 72
-
1
PUNYCODE_INITIAL_N = 0x80
-
1
PUNYCODE_DELIMITER = 0x2D
-
-
1
PUNYCODE_MAXINT = 1 << 64
-
-
1
PUNYCODE_PRINT_ASCII =
-
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
-
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" +
-
" !\"\#$%&'()*+,-./" +
-
"0123456789:;<=>?" +
-
"@ABCDEFGHIJKLMNO" +
-
"PQRSTUVWXYZ[\\]^_" +
-
"`abcdefghijklmno" +
-
"pqrstuvwxyz{|}~\n"
-
-
# Input is invalid.
-
1
class PunycodeBadInput < StandardError; end
-
# Output would exceed the space provided.
-
1
class PunycodeBigOutput < StandardError; end
-
# Input needs wider integers to process.
-
1
class PunycodeOverflow < StandardError; end
-
-
1
def self.punycode_encode(unicode)
-
input = unicode.unpack("U*")
-
output = [0] * (ACE_MAX_LENGTH + 1)
-
input_length = input.size
-
output_length = [ACE_MAX_LENGTH]
-
-
# Initialize the state
-
n = PUNYCODE_INITIAL_N
-
delta = out = 0
-
max_out = output_length[0]
-
bias = PUNYCODE_INITIAL_BIAS
-
-
# Handle the basic code points:
-
input_length.times do |j|
-
if punycode_basic?(input[j])
-
if max_out - out < 2
-
raise PunycodeBigOutput,
-
"Output would exceed the space provided."
-
end
-
output[out] = input[j]
-
out += 1
-
end
-
end
-
-
h = b = out
-
-
# h is the number of code points that have been handled, b is the
-
# number of basic code points, and out is the number of characters
-
# that have been output.
-
-
if b > 0
-
output[out] = PUNYCODE_DELIMITER
-
out += 1
-
end
-
-
# Main encoding loop:
-
-
while h < input_length
-
# All non-basic code points < n have been
-
# handled already. Find the next larger one:
-
-
m = PUNYCODE_MAXINT
-
input_length.times do |j|
-
m = input[j] if (n...m) === input[j]
-
end
-
-
# Increase delta enough to advance the decoder's
-
# <n,i> state to <m,0>, but guard against overflow:
-
-
if m - n > (PUNYCODE_MAXINT - delta) / (h + 1)
-
raise PunycodeOverflow, "Input needs wider integers to process."
-
end
-
delta += (m - n) * (h + 1)
-
n = m
-
-
input_length.times do |j|
-
# Punycode does not need to check whether input[j] is basic:
-
if input[j] < n
-
delta += 1
-
if delta == 0
-
raise PunycodeOverflow,
-
"Input needs wider integers to process."
-
end
-
end
-
-
if input[j] == n
-
# Represent delta as a generalized variable-length integer:
-
-
q = delta; k = PUNYCODE_BASE
-
while true
-
if out >= max_out
-
raise PunycodeBigOutput,
-
"Output would exceed the space provided."
-
end
-
t = (
-
if k <= bias
-
PUNYCODE_TMIN
-
elsif k >= bias + PUNYCODE_TMAX
-
PUNYCODE_TMAX
-
else
-
k - bias
-
end
-
)
-
break if q < t
-
output[out] =
-
punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t))
-
out += 1
-
q = (q - t) / (PUNYCODE_BASE - t)
-
k += PUNYCODE_BASE
-
end
-
-
output[out] = punycode_encode_digit(q)
-
out += 1
-
bias = punycode_adapt(delta, h + 1, h == b)
-
delta = 0
-
h += 1
-
end
-
end
-
-
delta += 1
-
n += 1
-
end
-
-
output_length[0] = out
-
-
outlen = out
-
outlen.times do |j|
-
c = output[j]
-
unless c >= 0 && c <= 127
-
raise Exception, "Invalid output char."
-
end
-
unless PUNYCODE_PRINT_ASCII[c]
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
end
-
-
output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "")
-
end
-
2
(class <<self; private :punycode_encode; end)
-
-
1
def self.punycode_decode(punycode)
-
input = []
-
output = []
-
-
if ACE_MAX_LENGTH * 2 < punycode.size
-
raise PunycodeBigOutput, "Output would exceed the space provided."
-
end
-
punycode.each_byte do |c|
-
unless c >= 0 && c <= 127
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
input.push(c)
-
end
-
-
input_length = input.length
-
output_length = [UNICODE_MAX_LENGTH]
-
-
# Initialize the state
-
n = PUNYCODE_INITIAL_N
-
-
out = i = 0
-
max_out = output_length[0]
-
bias = PUNYCODE_INITIAL_BIAS
-
-
# Handle the basic code points: Let b be the number of input code
-
# points before the last delimiter, or 0 if there is none, then
-
# copy the first b code points to the output.
-
-
b = 0
-
input_length.times do |j|
-
b = j if punycode_delimiter?(input[j])
-
end
-
if b > max_out
-
raise PunycodeBigOutput, "Output would exceed the space provided."
-
end
-
-
b.times do |j|
-
unless punycode_basic?(input[j])
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
output[out] = input[j]
-
out+=1
-
end
-
-
# Main decoding loop: Start just after the last delimiter if any
-
# basic code points were copied; start at the beginning otherwise.
-
-
in_ = b > 0 ? b + 1 : 0
-
while in_ < input_length
-
-
# in_ is the index of the next character to be consumed, and
-
# out is the number of code points in the output array.
-
-
# Decode a generalized variable-length integer into delta,
-
# which gets added to i. The overflow checking is easier
-
# if we increase i as we go, then subtract off its starting
-
# value at the end to obtain delta.
-
-
oldi = i; w = 1; k = PUNYCODE_BASE
-
while true
-
if in_ >= input_length
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
digit = punycode_decode_digit(input[in_])
-
in_+=1
-
if digit >= PUNYCODE_BASE
-
raise PunycodeBadInput, "Input is invalid."
-
end
-
if digit > (PUNYCODE_MAXINT - i) / w
-
raise PunycodeOverflow, "Input needs wider integers to process."
-
end
-
i += digit * w
-
t = (
-
if k <= bias
-
PUNYCODE_TMIN
-
elsif k >= bias + PUNYCODE_TMAX
-
PUNYCODE_TMAX
-
else
-
k - bias
-
end
-
)
-
break if digit < t
-
if w > PUNYCODE_MAXINT / (PUNYCODE_BASE - t)
-
raise PunycodeOverflow, "Input needs wider integers to process."
-
end
-
w *= PUNYCODE_BASE - t
-
k += PUNYCODE_BASE
-
end
-
-
bias = punycode_adapt(i - oldi, out + 1, oldi == 0)
-
-
# I was supposed to wrap around from out + 1 to 0,
-
# incrementing n each time, so we'll fix that now:
-
-
if i / (out + 1) > PUNYCODE_MAXINT - n
-
raise PunycodeOverflow, "Input needs wider integers to process."
-
end
-
n += i / (out + 1)
-
i %= out + 1
-
-
# Insert n at position i of the output:
-
-
# not needed for Punycode:
-
# raise PUNYCODE_INVALID_INPUT if decode_digit(n) <= base
-
if out >= max_out
-
raise PunycodeBigOutput, "Output would exceed the space provided."
-
end
-
-
#memmove(output + i + 1, output + i, (out - i) * sizeof *output)
-
output[i + 1, out - i] = output[i, out - i]
-
output[i] = n
-
i += 1
-
-
out += 1
-
end
-
-
output_length[0] = out
-
-
output.pack("U*")
-
end
-
2
(class <<self; private :punycode_decode; end)
-
-
1
def self.punycode_basic?(codepoint)
-
codepoint < 0x80
-
end
-
2
(class <<self; private :punycode_basic?; end)
-
-
1
def self.punycode_delimiter?(codepoint)
-
codepoint == PUNYCODE_DELIMITER
-
end
-
2
(class <<self; private :punycode_delimiter?; end)
-
-
1
def self.punycode_encode_digit(d)
-
d + 22 + 75 * ((d < 26) ? 1 : 0)
-
end
-
2
(class <<self; private :punycode_encode_digit; end)
-
-
# Returns the numeric value of a basic codepoint
-
# (for use in representing integers) in the range 0 to
-
# base - 1, or PUNYCODE_BASE if codepoint does not represent a value.
-
1
def self.punycode_decode_digit(codepoint)
-
if codepoint - 48 < 10
-
codepoint - 22
-
elsif codepoint - 65 < 26
-
codepoint - 65
-
elsif codepoint - 97 < 26
-
codepoint - 97
-
else
-
PUNYCODE_BASE
-
end
-
end
-
2
(class <<self; private :punycode_decode_digit; end)
-
-
# Bias adaptation method
-
1
def self.punycode_adapt(delta, numpoints, firsttime)
-
delta = firsttime ? delta / PUNYCODE_DAMP : delta >> 1
-
# delta >> 1 is a faster way of doing delta / 2
-
delta += delta / numpoints
-
difference = PUNYCODE_BASE - PUNYCODE_TMIN
-
-
k = 0
-
while delta > (difference * PUNYCODE_TMAX) / 2
-
delta /= difference
-
k += PUNYCODE_BASE
-
end
-
-
k + (difference + 1) * delta / (delta + PUNYCODE_SKEW)
-
end
-
2
(class <<self; private :punycode_adapt; end)
-
end
-
# :startdoc:
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2013 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
require "addressable/version"
-
1
require "addressable/uri"
-
-
1
module Addressable
-
##
-
# This is an implementation of a URI template based on
-
# RFC 6570 (http://tools.ietf.org/html/rfc6570).
-
1
class Template
-
# Constants used throughout the template code.
-
1
anything =
-
Addressable::URI::CharacterClasses::RESERVED +
-
Addressable::URI::CharacterClasses::UNRESERVED
-
-
-
1
variable_char_class =
-
Addressable::URI::CharacterClasses::ALPHA +
-
Addressable::URI::CharacterClasses::DIGIT + '_'
-
-
1
var_char =
-
"(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
-
1
RESERVED =
-
"(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
-
1
UNRESERVED =
-
"(?:[#{
-
Addressable::URI::CharacterClasses::UNRESERVED
-
}]|%[a-fA-F0-9][a-fA-F0-9])"
-
1
variable =
-
"(?:#{var_char}(?:\\.?#{var_char})*)"
-
1
varspec =
-
"(?:(#{variable})(\\*|:\\d+)?)"
-
1
VARNAME =
-
/^#{variable}$/
-
1
VARSPEC =
-
/^#{varspec}$/
-
1
VARIABLE_LIST =
-
/^#{varspec}(?:,#{varspec})*$/
-
1
operator =
-
"+#./;?&=,!@|"
-
1
EXPRESSION =
-
/\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
-
-
-
1
LEADERS = {
-
'?' => '?',
-
'/' => '/',
-
'#' => '#',
-
'.' => '.',
-
';' => ';',
-
'&' => '&'
-
}
-
1
JOINERS = {
-
'?' => '&',
-
'.' => '.',
-
';' => ';',
-
'&' => '&',
-
'/' => '/'
-
}
-
-
##
-
# Raised if an invalid template value is supplied.
-
1
class InvalidTemplateValueError < StandardError
-
end
-
-
##
-
# Raised if an invalid template operator is used in a pattern.
-
1
class InvalidTemplateOperatorError < StandardError
-
end
-
-
##
-
# Raised if an invalid template operator is used in a pattern.
-
1
class TemplateOperatorAbortedError < StandardError
-
end
-
-
##
-
# This class represents the data that is extracted when a Template
-
# is matched against a URI.
-
1
class MatchData
-
##
-
# Creates a new MatchData object.
-
# MatchData objects should never be instantiated directly.
-
#
-
# @param [Addressable::URI] uri
-
# The URI that the template was matched against.
-
1
def initialize(uri, template, mapping)
-
@uri = uri.dup.freeze
-
@template = template
-
@mapping = mapping.dup.freeze
-
end
-
-
##
-
# @return [Addressable::URI]
-
# The URI that the Template was matched against.
-
1
attr_reader :uri
-
-
##
-
# @return [Addressable::Template]
-
# The Template used for the match.
-
1
attr_reader :template
-
-
##
-
# @return [Hash]
-
# The mapping that resulted from the match.
-
# Note that this mapping does not include keys or values for
-
# variables that appear in the Template, but are not present
-
# in the URI.
-
1
attr_reader :mapping
-
-
##
-
# @return [Array]
-
# The list of variables that were present in the Template.
-
# Note that this list will include variables which do not appear
-
# in the mapping because they were not present in URI.
-
1
def variables
-
self.template.variables
-
end
-
1
alias_method :keys, :variables
-
1
alias_method :names, :variables
-
-
##
-
# @return [Array]
-
# The list of values that were captured by the Template.
-
# Note that this list will include nils for any variables which
-
# were in the Template, but did not appear in the URI.
-
1
def values
-
@values ||= self.variables.inject([]) do |accu, key|
-
accu << self.mapping[key]
-
accu
-
end
-
end
-
1
alias_method :captures, :values
-
-
##
-
# Accesses captured values by name or by index.
-
#
-
# @param [String, Symbol, Fixnum] key
-
# Capture index or name. Note that when accessing by with index
-
# of 0, the full URI will be returned. The intention is to mimic
-
# the ::MatchData#[] behavior.
-
#
-
# @param [#to_int, nil] len
-
# If provided, an array of values will be returend with the given
-
# parameter used as length.
-
#
-
# @return [Array, String, nil]
-
# The captured value corresponding to the index or name. If the
-
# value was not provided or the key is unknown, nil will be
-
# returned.
-
#
-
# If the second parameter is provided, an array of that length will
-
# be returned instead.
-
1
def [](key, len = nil)
-
if len
-
to_a[key, len]
-
elsif String === key or Symbol === key
-
mapping[key.to_s]
-
else
-
to_a[key]
-
end
-
end
-
-
##
-
# @return [Array]
-
# Array with the matched URI as first element followed by the captured
-
# values.
-
1
def to_a
-
[to_s, *values]
-
end
-
-
##
-
# @return [String]
-
# The matched URI as String.
-
1
def to_s
-
uri.to_s
-
end
-
1
alias_method :string, :to_s
-
-
# Returns multiple captured values at once.
-
#
-
# @param [String, Symbol, Fixnum] *indexes
-
# Indices of the captures to be returned
-
#
-
# @return [Array]
-
# Values corresponding to given indices.
-
#
-
# @see Addressable::Template::MatchData#[]
-
1
def values_at(*indexes)
-
indexes.map { |i| self[i] }
-
end
-
-
##
-
# Returns a <tt>String</tt> representation of the MatchData's state.
-
#
-
# @return [String] The MatchData's state, as a <tt>String</tt>.
-
1
def inspect
-
sprintf("#<%s:%#0x RESULT:%s>",
-
self.class.to_s, self.object_id, self.mapping.inspect)
-
end
-
-
##
-
# Dummy method for code expecting a ::MatchData instance
-
#
-
# @return [String] An empty string.
-
1
def pre_match
-
""
-
end
-
1
alias_method :post_match, :pre_match
-
end
-
-
##
-
# Creates a new <tt>Addressable::Template</tt> object.
-
#
-
# @param [#to_str] pattern The URI Template pattern.
-
#
-
# @return [Addressable::Template] The initialized Template object.
-
1
def initialize(pattern)
-
if !pattern.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{pattern.class} into String."
-
end
-
@pattern = pattern.to_str.freeze
-
end
-
-
##
-
# @return [String] The Template object's pattern.
-
1
attr_reader :pattern
-
-
##
-
# Returns a <tt>String</tt> representation of the Template object's state.
-
#
-
# @return [String] The Template object's state, as a <tt>String</tt>.
-
1
def inspect
-
sprintf("#<%s:%#0x PATTERN:%s>",
-
self.class.to_s, self.object_id, self.pattern)
-
end
-
-
##
-
# Returns <code>true</code> if the Template objects are equal. This method
-
# does NOT normalize either Template before doing the comparison.
-
#
-
# @param [Object] template The Template to compare.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the Templates are equivalent, <code>false</code>
-
# otherwise.
-
1
def ==(template)
-
return false unless template.kind_of?(Template)
-
return self.pattern == template.pattern
-
end
-
-
##
-
# Addressable::Template makes no distinction between `==` and `eql?`.
-
#
-
# @see #==
-
1
alias_method :eql?, :==
-
-
##
-
# Extracts a mapping from the URI using a URI Template pattern.
-
#
-
# @param [Addressable::URI, #to_str] uri
-
# The URI to extract from.
-
#
-
# @param [#restore, #match] processor
-
# A template processor object may optionally be supplied.
-
#
-
# The object should respond to either the <tt>restore</tt> or
-
# <tt>match</tt> messages or both. The <tt>restore</tt> method should
-
# take two parameters: `[String] name` and `[String] value`.
-
# The <tt>restore</tt> method should reverse any transformations that
-
# have been performed on the value to ensure a valid URI.
-
# The <tt>match</tt> method should take a single
-
# parameter: `[String] name`. The <tt>match</tt> method should return
-
# a <tt>String</tt> containing a regular expression capture group for
-
# matching on that particular variable. The default value is `".*?"`.
-
# The <tt>match</tt> method has no effect on multivariate operator
-
# expansions.
-
#
-
# @return [Hash, NilClass]
-
# The <tt>Hash</tt> mapping that was extracted from the URI, or
-
# <tt>nil</tt> if the URI didn't match the template.
-
#
-
# @example
-
# class ExampleProcessor
-
# def self.restore(name, value)
-
# return value.gsub(/\+/, " ") if name == "query"
-
# return value
-
# end
-
#
-
# def self.match(name)
-
# return ".*?" if name == "first"
-
# return ".*"
-
# end
-
# end
-
#
-
# uri = Addressable::URI.parse(
-
# "http://example.com/search/an+example+search+query/"
-
# )
-
# Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).extract(uri, ExampleProcessor)
-
# #=> {"query" => "an example search query"}
-
#
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
-
# Addressable::Template.new(
-
# "http://example.com/{first}/{second}/"
-
# ).extract(uri, ExampleProcessor)
-
# #=> {"first" => "a", "second" => "b/c"}
-
#
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
-
# Addressable::Template.new(
-
# "http://example.com/{first}/{-list|/|second}/"
-
# ).extract(uri)
-
# #=> {"first" => "a", "second" => ["b", "c"]}
-
1
def extract(uri, processor=nil)
-
match_data = self.match(uri, processor)
-
return (match_data ? match_data.mapping : nil)
-
end
-
-
##
-
# Extracts match data from the URI using a URI Template pattern.
-
#
-
# @param [Addressable::URI, #to_str] uri
-
# The URI to extract from.
-
#
-
# @param [#restore, #match] processor
-
# A template processor object may optionally be supplied.
-
#
-
# The object should respond to either the <tt>restore</tt> or
-
# <tt>match</tt> messages or both. The <tt>restore</tt> method should
-
# take two parameters: `[String] name` and `[String] value`.
-
# The <tt>restore</tt> method should reverse any transformations that
-
# have been performed on the value to ensure a valid URI.
-
# The <tt>match</tt> method should take a single
-
# parameter: `[String] name`. The <tt>match</tt> method should return
-
# a <tt>String</tt> containing a regular expression capture group for
-
# matching on that particular variable. The default value is `".*?"`.
-
# The <tt>match</tt> method has no effect on multivariate operator
-
# expansions.
-
#
-
# @return [Hash, NilClass]
-
# The <tt>Hash</tt> mapping that was extracted from the URI, or
-
# <tt>nil</tt> if the URI didn't match the template.
-
#
-
# @example
-
# class ExampleProcessor
-
# def self.restore(name, value)
-
# return value.gsub(/\+/, " ") if name == "query"
-
# return value
-
# end
-
#
-
# def self.match(name)
-
# return ".*?" if name == "first"
-
# return ".*"
-
# end
-
# end
-
#
-
# uri = Addressable::URI.parse(
-
# "http://example.com/search/an+example+search+query/"
-
# )
-
# match = Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).match(uri, ExampleProcessor)
-
# match.variables
-
# #=> ["query"]
-
# match.captures
-
# #=> ["an example search query"]
-
#
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
-
# match = Addressable::Template.new(
-
# "http://example.com/{first}/{+second}/"
-
# ).match(uri, ExampleProcessor)
-
# match.variables
-
# #=> ["first", "second"]
-
# match.captures
-
# #=> ["a", "b/c"]
-
#
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
-
# match = Addressable::Template.new(
-
# "http://example.com/{first}{/second*}/"
-
# ).match(uri)
-
# match.variables
-
# #=> ["first", "second"]
-
# match.captures
-
# #=> ["a", ["b", "c"]]
-
1
def match(uri, processor=nil)
-
uri = Addressable::URI.parse(uri)
-
mapping = {}
-
-
# First, we need to process the pattern, and extract the values.
-
expansions, expansion_regexp =
-
parse_template_pattern(pattern, processor)
-
-
return nil unless uri.to_str.match(expansion_regexp)
-
unparsed_values = uri.to_str.scan(expansion_regexp).flatten
-
-
if uri.to_str == pattern
-
return Addressable::Template::MatchData.new(uri, self, mapping)
-
elsif expansions.size > 0
-
index = 0
-
expansions.each do |expansion|
-
_, operator, varlist = *expansion.match(EXPRESSION)
-
varlist.split(',').each do |varspec|
-
_, name, modifier = *varspec.match(VARSPEC)
-
mapping[name] ||= nil
-
case operator
-
when nil, '+', '#', '/', '.'
-
unparsed_value = unparsed_values[index]
-
name = varspec[VARSPEC, 1]
-
value = unparsed_value
-
value = value.split(JOINERS[operator]) if value && modifier == '*'
-
when ';', '?', '&'
-
if modifier == '*'
-
if unparsed_values[index]
-
value = unparsed_values[index].split(JOINERS[operator])
-
value = value.inject({}) do |acc, v|
-
key, val = v.split('=')
-
val = "" if val.nil?
-
acc[key] = val
-
acc
-
end
-
end
-
else
-
if (unparsed_values[index])
-
name, value = unparsed_values[index].split('=')
-
value = "" if value.nil?
-
end
-
end
-
end
-
if processor != nil && processor.respond_to?(:restore)
-
value = processor.restore(name, value)
-
end
-
if processor == nil
-
if value.is_a?(Hash)
-
value = value.inject({}){|acc, (k, v)|
-
acc[Addressable::URI.unencode_component(k)] =
-
Addressable::URI.unencode_component(v)
-
acc
-
}
-
elsif value.is_a?(Array)
-
value = value.map{|v| Addressable::URI.unencode_component(v) }
-
else
-
value = Addressable::URI.unencode_component(value)
-
end
-
end
-
if !mapping.has_key?(name) || mapping[name].nil?
-
# Doesn't exist, set to value (even if value is nil)
-
mapping[name] = value
-
end
-
index = index + 1
-
end
-
end
-
return Addressable::Template::MatchData.new(uri, self, mapping)
-
else
-
return nil
-
end
-
end
-
-
##
-
# Expands a URI template into another URI template.
-
#
-
# @param [Hash] mapping The mapping that corresponds to the pattern.
-
# @param [#validate, #transform] processor
-
# An optional processor object may be supplied.
-
#
-
# The object should respond to either the <tt>validate</tt> or
-
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
-
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
-
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
-
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
-
# exception will be raised if the value is invalid. The <tt>transform</tt>
-
# method should return the transformed variable value as a <tt>String</tt>.
-
# If a <tt>transform</tt> method is used, the value will not be percent
-
# encoded automatically. Unicode normalization will be performed both
-
# before and after sending the value to the transform method.
-
#
-
# @return [Addressable::Template] The partially expanded URI template.
-
#
-
# @example
-
# Addressable::Template.new(
-
# "http://example.com/{one}/{two}/"
-
# ).partial_expand({"one" => "1"}).pattern
-
# #=> "http://example.com/1/{two}/"
-
#
-
# Addressable::Template.new(
-
# "http://example.com/{?one,two}/"
-
# ).partial_expand({"one" => "1"}).pattern
-
# #=> "http://example.com/?one=1{&two}/"
-
#
-
# Addressable::Template.new(
-
# "http://example.com/{?one,two,three}/"
-
# ).partial_expand({"one" => "1", "three" => 3}).pattern
-
# #=> "http://example.com/?one=1{&two}&three=3"
-
1
def partial_expand(mapping, processor=nil)
-
result = self.pattern.dup
-
mapping = normalize_keys(mapping)
-
result.gsub!( EXPRESSION ) do |capture|
-
transform_partial_capture(mapping, capture, processor)
-
end
-
return Addressable::Template.new(result)
-
end
-
-
##
-
# Expands a URI template into a full URI.
-
#
-
# @param [Hash] mapping The mapping that corresponds to the pattern.
-
# @param [#validate, #transform] processor
-
# An optional processor object may be supplied.
-
#
-
# The object should respond to either the <tt>validate</tt> or
-
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
-
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
-
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
-
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
-
# exception will be raised if the value is invalid. The <tt>transform</tt>
-
# method should return the transformed variable value as a <tt>String</tt>.
-
# If a <tt>transform</tt> method is used, the value will not be percent
-
# encoded automatically. Unicode normalization will be performed both
-
# before and after sending the value to the transform method.
-
#
-
# @return [Addressable::URI] The expanded URI template.
-
#
-
# @example
-
# class ExampleProcessor
-
# def self.validate(name, value)
-
# return !!(value =~ /^[\w ]+$/) if name == "query"
-
# return true
-
# end
-
#
-
# def self.transform(name, value)
-
# return value.gsub(/ /, "+") if name == "query"
-
# return value
-
# end
-
# end
-
#
-
# Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).expand(
-
# {"query" => "an example search query"},
-
# ExampleProcessor
-
# ).to_str
-
# #=> "http://example.com/search/an+example+search+query/"
-
#
-
# Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).expand(
-
# {"query" => "an example search query"}
-
# ).to_str
-
# #=> "http://example.com/search/an%20example%20search%20query/"
-
#
-
# Addressable::Template.new(
-
# "http://example.com/search/{query}/"
-
# ).expand(
-
# {"query" => "bogus!"},
-
# ExampleProcessor
-
# ).to_str
-
# #=> Addressable::Template::InvalidTemplateValueError
-
1
def expand(mapping, processor=nil)
-
result = self.pattern.dup
-
mapping = normalize_keys(mapping)
-
result.gsub!( EXPRESSION ) do |capture|
-
transform_capture(mapping, capture, processor)
-
end
-
return Addressable::URI.parse(result)
-
end
-
-
##
-
# Returns an Array of variables used within the template pattern.
-
# The variables are listed in the Array in the order they appear within
-
# the pattern. Multiple occurrences of a variable within a pattern are
-
# not represented in this Array.
-
#
-
# @return [Array] The variables present in the template's pattern.
-
1
def variables
-
@variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
-
end
-
1
alias_method :keys, :variables
-
-
##
-
# Returns a mapping of variables to their default values specified
-
# in the template. Variables without defaults are not returned.
-
#
-
# @return [Hash] Mapping of template variables to their defaults
-
1
def variable_defaults
-
@variable_defaults ||=
-
Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
-
end
-
-
1
private
-
1
def ordered_variable_defaults
-
@ordered_variable_defaults ||= (
-
expansions, _ = parse_template_pattern(pattern)
-
expansions.map do |capture|
-
_, _, varlist = *capture.match(EXPRESSION)
-
varlist.split(',').map do |varspec|
-
varspec[VARSPEC, 1]
-
end
-
end.flatten
-
)
-
end
-
-
-
##
-
# Loops through each capture and expands any values available in mapping
-
#
-
# @param [Hash] mapping
-
# Set of keys to expand
-
# @param [String] capture
-
# The expression to expand
-
# @param [#validate, #transform] processor
-
# An optional processor object may be supplied.
-
#
-
# The object should respond to either the <tt>validate</tt> or
-
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
-
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
-
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
-
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
-
# will be raised if the value is invalid. The <tt>transform</tt> method
-
# should return the transformed variable value as a <tt>String</tt>. If a
-
# <tt>transform</tt> method is used, the value will not be percent encoded
-
# automatically. Unicode normalization will be performed both before and
-
# after sending the value to the transform method.
-
#
-
# @return [String] The expanded expression
-
1
def transform_partial_capture(mapping, capture, processor = nil)
-
_, operator, varlist = *capture.match(EXPRESSION)
-
is_first = true
-
varlist.split(',').inject('') do |acc, varspec|
-
_, name, _ = *varspec.match(VARSPEC)
-
value = mapping[name]
-
if value
-
operator = '&' if !is_first && operator == '?'
-
acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor)
-
else
-
operator = '&' if !is_first && operator == '?'
-
acc << "{#{operator}#{varspec}}"
-
end
-
is_first = false
-
acc
-
end
-
end
-
-
##
-
# Transforms a mapped value so that values can be substituted into the
-
# template.
-
#
-
# @param [Hash] mapping The mapping to replace captures
-
# @param [String] capture
-
# The expression to replace
-
# @param [#validate, #transform] processor
-
# An optional processor object may be supplied.
-
#
-
# The object should respond to either the <tt>validate</tt> or
-
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
-
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
-
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
-
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
-
# will be raised if the value is invalid. The <tt>transform</tt> method
-
# should return the transformed variable value as a <tt>String</tt>. If a
-
# <tt>transform</tt> method is used, the value will not be percent encoded
-
# automatically. Unicode normalization will be performed both before and
-
# after sending the value to the transform method.
-
#
-
# @return [String] The expanded expression
-
1
def transform_capture(mapping, capture, processor=nil)
-
_, operator, varlist = *capture.match(EXPRESSION)
-
return_value = varlist.split(',').inject([]) do |acc, varspec|
-
_, name, modifier = *varspec.match(VARSPEC)
-
value = mapping[name]
-
unless value == nil || value == {}
-
allow_reserved = %w(+ #).include?(operator)
-
# Common primitives where the .to_s output is well-defined
-
if Numeric === value || Symbol === value ||
-
value == true || value == false
-
value = value.to_s
-
end
-
length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
-
-
unless (Hash === value) ||
-
value.respond_to?(:to_ary) || value.respond_to?(:to_str)
-
raise TypeError,
-
"Can't convert #{value.class} into String or Array."
-
end
-
-
value = normalize_value(value)
-
-
if processor == nil || !processor.respond_to?(:transform)
-
# Handle percent escaping
-
if allow_reserved
-
encode_map =
-
Addressable::URI::CharacterClasses::RESERVED +
-
Addressable::URI::CharacterClasses::UNRESERVED
-
else
-
encode_map = Addressable::URI::CharacterClasses::UNRESERVED
-
end
-
if value.kind_of?(Array)
-
transformed_value = value.map do |val|
-
if length
-
Addressable::URI.encode_component(val[0...length], encode_map)
-
else
-
Addressable::URI.encode_component(val, encode_map)
-
end
-
end
-
unless modifier == "*"
-
transformed_value = transformed_value.join(',')
-
end
-
elsif value.kind_of?(Hash)
-
transformed_value = value.map do |key, val|
-
if modifier == "*"
-
"#{
-
Addressable::URI.encode_component( key, encode_map)
-
}=#{
-
Addressable::URI.encode_component( val, encode_map)
-
}"
-
else
-
"#{
-
Addressable::URI.encode_component( key, encode_map)
-
},#{
-
Addressable::URI.encode_component( val, encode_map)
-
}"
-
end
-
end
-
unless modifier == "*"
-
transformed_value = transformed_value.join(',')
-
end
-
else
-
if length
-
transformed_value = Addressable::URI.encode_component(
-
value[0...length], encode_map)
-
else
-
transformed_value = Addressable::URI.encode_component(
-
value, encode_map)
-
end
-
end
-
end
-
-
# Process, if we've got a processor
-
if processor != nil
-
if processor.respond_to?(:validate)
-
if !processor.validate(name, value)
-
display_value = value.kind_of?(Array) ? value.inspect : value
-
raise InvalidTemplateValueError,
-
"#{name}=#{display_value} is an invalid template value."
-
end
-
end
-
if processor.respond_to?(:transform)
-
transformed_value = processor.transform(name, value)
-
transformed_value = normalize_value(transformed_value)
-
end
-
end
-
acc << [name, transformed_value]
-
end
-
acc
-
end
-
return "" if return_value.empty?
-
join_values(operator, return_value)
-
end
-
-
##
-
# Takes a set of values, and joins them together based on the
-
# operator.
-
#
-
# @param [String, Nil] operator One of the operators from the set
-
# (?,&,+,#,;,/,.), or nil if there wasn't one.
-
# @param [Array] return_value
-
# The set of return values (as [variable_name, value] tuples) that will
-
# be joined together.
-
#
-
# @return [String] The transformed mapped value
-
1
def join_values(operator, return_value)
-
leader = LEADERS.fetch(operator, '')
-
joiner = JOINERS.fetch(operator, ',')
-
case operator
-
when '&', '?'
-
leader + return_value.map{|k,v|
-
if v.is_a?(Array) && v.first =~ /=/
-
v.join(joiner)
-
elsif v.is_a?(Array)
-
v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner)
-
else
-
"#{k}=#{v}"
-
end
-
}.join(joiner)
-
when ';'
-
return_value.map{|k,v|
-
if v.is_a?(Array) && v.first =~ /=/
-
';' + v.join(";")
-
elsif v.is_a?(Array)
-
';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";")
-
else
-
v && v != '' ? ";#{k}=#{v}" : ";#{k}"
-
end
-
}.join
-
else
-
leader + return_value.map{|k,v| v}.join(joiner)
-
end
-
end
-
-
##
-
# Takes a set of values, and joins them together based on the
-
# operator.
-
#
-
# @param [Hash, Array, String] value
-
# Normalizes keys and values with IDNA#unicode_normalize_kc
-
#
-
# @return [Hash, Array, String] The normalized values
-
1
def normalize_value(value)
-
unless value.is_a?(Hash)
-
value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
-
end
-
-
# Handle unicode normalization
-
if value.kind_of?(Array)
-
value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
-
elsif value.kind_of?(Hash)
-
value = value.inject({}) { |acc, (k, v)|
-
acc[Addressable::IDNA.unicode_normalize_kc(k)] =
-
Addressable::IDNA.unicode_normalize_kc(v)
-
acc
-
}
-
else
-
value = Addressable::IDNA.unicode_normalize_kc(value)
-
end
-
value
-
end
-
-
##
-
# Generates a hash with string keys
-
#
-
# @param [Hash] mapping A mapping hash to normalize
-
#
-
# @return [Hash]
-
# A hash with stringified keys
-
1
def normalize_keys(mapping)
-
return mapping.inject({}) do |accu, pair|
-
name, value = pair
-
if Symbol === name
-
name = name.to_s
-
elsif name.respond_to?(:to_str)
-
name = name.to_str
-
else
-
raise TypeError,
-
"Can't convert #{name.class} into String."
-
end
-
accu[name] = value
-
accu
-
end
-
end
-
-
##
-
# Generates the <tt>Regexp</tt> that parses a template pattern.
-
#
-
# @param [String] pattern The URI template pattern.
-
# @param [#match] processor The template processor to use.
-
#
-
# @return [Regexp]
-
# A regular expression which may be used to parse a template pattern.
-
1
def parse_template_pattern(pattern, processor=nil)
-
# Escape the pattern. The two gsubs restore the escaped curly braces
-
# back to their original form. Basically, escape everything that isn't
-
# within an expansion.
-
escaped_pattern = Regexp.escape(
-
pattern
-
).gsub(/\\\{(.*?)\\\}/) do |escaped|
-
escaped.gsub(/\\(.)/, "\\1")
-
end
-
-
expansions = []
-
-
# Create a regular expression that captures the values of the
-
# variables in the URI.
-
regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
-
-
expansions << expansion
-
_, operator, varlist = *expansion.match(EXPRESSION)
-
leader = Regexp.escape(LEADERS.fetch(operator, ''))
-
joiner = Regexp.escape(JOINERS.fetch(operator, ','))
-
combined = varlist.split(',').map do |varspec|
-
_, name, modifier = *varspec.match(VARSPEC)
-
-
result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
-
if result
-
"(#{ result })"
-
else
-
group = case operator
-
when '+'
-
"#{ RESERVED }*?"
-
when '#'
-
"#{ RESERVED }*?"
-
when '/'
-
"#{ UNRESERVED }*?"
-
when '.'
-
"#{ UNRESERVED.gsub('\.', '') }*?"
-
when ';'
-
"#{ UNRESERVED }*=?#{ UNRESERVED }*?"
-
when '?'
-
"#{ UNRESERVED }*=#{ UNRESERVED }*?"
-
when '&'
-
"#{ UNRESERVED }*=#{ UNRESERVED }*?"
-
else
-
"#{ UNRESERVED }*?"
-
end
-
if modifier == '*'
-
"(#{group}(?:#{joiner}?#{group})*)?"
-
else
-
"(#{group})?"
-
end
-
end
-
end.join("#{joiner}?")
-
"(?:|#{leader}#{combined})"
-
end
-
-
# Ensure that the regular expression matches the whole URI.
-
regexp_string = "^#{regexp_string}$"
-
return expansions, Regexp.new(regexp_string)
-
end
-
-
end
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2013 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
1
require "addressable/version"
-
1
require "addressable/idna"
-
-
##
-
# Addressable is a library for processing links and URIs.
-
1
module Addressable
-
##
-
# This is an implementation of a URI parser based on
-
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
-
# <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>.
-
1
class URI
-
##
-
# Raised if something other than a uri is supplied.
-
1
class InvalidURIError < StandardError
-
end
-
-
##
-
# Container for the character classes specified in
-
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
-
1
module CharacterClasses
-
1
ALPHA = "a-zA-Z"
-
1
DIGIT = "0-9"
-
1
GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
-
1
SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
-
1
RESERVED = GEN_DELIMS + SUB_DELIMS
-
1
UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
-
1
PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
-
1
SCHEME = ALPHA + DIGIT + "\\-\\+\\."
-
1
AUTHORITY = PCHAR
-
1
PATH = PCHAR + "\\/"
-
1
QUERY = PCHAR + "\\/\\?"
-
1
FRAGMENT = PCHAR + "\\/\\?"
-
end
-
-
1
SLASH = '/'
-
1
EMPTY_STR = ''
-
-
1
URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
-
-
1
PORT_MAPPING = {
-
"http" => 80,
-
"https" => 443,
-
"ftp" => 21,
-
"tftp" => 69,
-
"sftp" => 22,
-
"ssh" => 22,
-
"svn+ssh" => 22,
-
"telnet" => 23,
-
"nntp" => 119,
-
"gopher" => 70,
-
"wais" => 210,
-
"ldap" => 389,
-
"prospero" => 1525
-
}
-
-
##
-
# Returns a URI object based on the parsed string.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI string to parse.
-
# No parsing is performed if the object is already an
-
# <code>Addressable::URI</code>.
-
#
-
# @return [Addressable::URI] The parsed URI.
-
1
def self.parse(uri)
-
# If we were given nil, return nil.
-
10
return nil unless uri
-
# If a URI object is passed, just return itself.
-
10
return uri.dup if uri.kind_of?(self)
-
-
# If a URI object of the Ruby standard library variety is passed,
-
# convert it to a string, then parse the string.
-
# We do the check this way because we don't want to accidentally
-
# cause a missing constant exception to be thrown.
-
10
if uri.class.name =~ /^URI\b/
-
uri = uri.to_s
-
end
-
-
# Otherwise, convert to a String
-
begin
-
uri = uri.to_str
-
rescue TypeError, NoMethodError
-
raise TypeError, "Can't convert #{uri.class} into String."
-
10
end if not uri.is_a? String
-
-
# This Regexp supplied as an example in RFC 3986, and it works great.
-
10
scan = uri.scan(URIREGEX)
-
10
fragments = scan[0]
-
10
scheme = fragments[1]
-
10
authority = fragments[3]
-
10
path = fragments[4]
-
10
query = fragments[6]
-
10
fragment = fragments[8]
-
10
user = nil
-
10
password = nil
-
10
host = nil
-
10
port = nil
-
10
if authority != nil
-
# The Regexp above doesn't split apart the authority.
-
10
userinfo = authority[/^([^\[\]]*)@/, 1]
-
10
if userinfo != nil
-
user = userinfo.strip[/^([^:]*):?/, 1]
-
password = userinfo.strip[/:(.*)$/, 1]
-
end
-
10
host = authority.gsub(
-
/^([^\[\]]*)@/, EMPTY_STR
-
).gsub(
-
/:([^:@\[\]]*?)$/, EMPTY_STR
-
)
-
10
port = authority[/:([^:@\[\]]*?)$/, 1]
-
end
-
10
if port == EMPTY_STR
-
port = nil
-
end
-
-
10
return new(
-
:scheme => scheme,
-
:user => user,
-
:password => password,
-
:host => host,
-
:port => port,
-
:path => path,
-
:query => query,
-
:fragment => fragment
-
)
-
end
-
-
##
-
# Converts an input to a URI. The input does not have to be a valid
-
# URI — the method will use heuristics to guess what URI was intended.
-
# This is not standards-compliant, merely user-friendly.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI string to parse.
-
# No parsing is performed if the object is already an
-
# <code>Addressable::URI</code>.
-
# @param [Hash] hints
-
# A <code>Hash</code> of hints to the heuristic parser.
-
# Defaults to <code>{:scheme => "http"}</code>.
-
#
-
# @return [Addressable::URI] The parsed URI.
-
1
def self.heuristic_parse(uri, hints={})
-
# If we were given nil, return nil.
-
4
return nil unless uri
-
# If a URI object is passed, just return itself.
-
4
return uri.dup if uri.kind_of?(self)
-
-
# If a URI object of the Ruby standard library variety is passed,
-
# convert it to a string, then parse the string.
-
# We do the check this way because we don't want to accidentally
-
# cause a missing constant exception to be thrown.
-
4
if uri.class.name =~ /^URI\b/
-
uri = uri.to_s
-
end
-
-
4
if !uri.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end
-
# Otherwise, convert to a String
-
4
uri = uri.to_str.dup
-
4
hints = {
-
:scheme => "http"
-
}.merge(hints)
-
4
case uri
-
when /^http:\/+/
-
uri.gsub!(/^http:\/+/, "http://")
-
when /^https:\/+/
-
4
uri.gsub!(/^https:\/+/, "https://")
-
when /^feed:\/+http:\/+/
-
uri.gsub!(/^feed:\/+http:\/+/, "feed:http://")
-
when /^feed:\/+/
-
uri.gsub!(/^feed:\/+/, "feed://")
-
when /^file:\/+/
-
uri.gsub!(/^file:\/+/, "file:///")
-
when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
-
uri.gsub!(/^/, hints[:scheme] + "://")
-
end
-
4
parsed = self.parse(uri)
-
4
if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
-
parsed = self.parse(hints[:scheme] + "://" + uri)
-
end
-
4
if parsed.path.include?(".")
-
new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
-
if new_host
-
parsed.defer_validation do
-
new_path = parsed.path.gsub(
-
Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
-
parsed.host = new_host
-
parsed.path = new_path
-
parsed.scheme = hints[:scheme] unless parsed.scheme
-
end
-
end
-
end
-
4
return parsed
-
end
-
-
##
-
# Converts a path to a file scheme URI. If the path supplied is
-
# relative, it will be returned as a relative URI. If the path supplied
-
# is actually a non-file URI, it will parse the URI as if it had been
-
# parsed with <code>Addressable::URI.parse</code>. Handles all of the
-
# various Microsoft-specific formats for specifying paths.
-
#
-
# @param [String, Addressable::URI, #to_str] path
-
# Typically a <code>String</code> path to a file or directory, but
-
# will return a sensible return value if an absolute URI is supplied
-
# instead.
-
#
-
# @return [Addressable::URI]
-
# The parsed file scheme URI or the original URI if some other URI
-
# scheme was provided.
-
#
-
# @example
-
# base = Addressable::URI.convert_path("/absolute/path/")
-
# uri = Addressable::URI.convert_path("relative/path")
-
# (base + uri).to_s
-
# #=> "file:///absolute/path/relative/path"
-
#
-
# Addressable::URI.convert_path(
-
# "c:\\windows\\My Documents 100%20\\foo.txt"
-
# ).to_s
-
# #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
-
#
-
# Addressable::URI.convert_path("http://example.com/").to_s
-
# #=> "http://example.com/"
-
1
def self.convert_path(path)
-
# If we were given nil, return nil.
-
return nil unless path
-
# If a URI object is passed, just return itself.
-
return path if path.kind_of?(self)
-
if !path.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{path.class} into String."
-
end
-
# Otherwise, convert to a String
-
path = path.to_str.strip
-
-
path.gsub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/
-
path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/
-
uri = self.parse(path)
-
-
if uri.scheme == nil
-
# Adjust windows-style uris
-
uri.path.gsub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
-
"/#{$1.downcase}:/"
-
end
-
uri.path.gsub!(/\\/, SLASH)
-
if File.exists?(uri.path) &&
-
File.stat(uri.path).directory?
-
uri.path.gsub!(/\/$/, EMPTY_STR)
-
uri.path = uri.path + '/'
-
end
-
-
# If the path is absolute, set the scheme and host.
-
if uri.path =~ /^\//
-
uri.scheme = "file"
-
uri.host = EMPTY_STR
-
end
-
uri.normalize!
-
end
-
-
return uri
-
end
-
-
##
-
# Joins several URIs together.
-
#
-
# @param [String, Addressable::URI, #to_str] *uris
-
# The URIs to join.
-
#
-
# @return [Addressable::URI] The joined URI.
-
#
-
# @example
-
# base = "http://example.com/"
-
# uri = Addressable::URI.parse("relative/path")
-
# Addressable::URI.join(base, uri)
-
# #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
-
1
def self.join(*uris)
-
uri_objects = uris.collect do |uri|
-
if !uri.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end
-
uri.kind_of?(self) ? uri : self.parse(uri.to_str)
-
end
-
result = uri_objects.shift.dup
-
for uri in uri_objects
-
result.join!(uri)
-
end
-
return result
-
end
-
-
##
-
# Percent encodes a URI component.
-
#
-
# @param [String, #to_str] component The URI component to encode.
-
#
-
# @param [String, Regexp] character_class
-
# The characters which are not percent encoded. If a <code>String</code>
-
# is passed, the <code>String</code> must be formatted as a regular
-
# expression character class. (Do not include the surrounding square
-
# brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
-
# everything but the letters 'b' through 'z' and the numbers '0' through
-
# '9' to be percent encoded. If a <code>Regexp</code> is passed, the
-
# value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A set of
-
# useful <code>String</code> values may be found in the
-
# <code>Addressable::URI::CharacterClasses</code> module. The default
-
# value is the reserved plus unreserved character classes specified in
-
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
-
#
-
# @param [Regexp] upcase_encoded
-
# A string of characters that may already be percent encoded, and whose
-
# encodings should be upcased. This allows normalization of percent
-
# encodings for characters not included in the
-
# <code>character_class</code>.
-
#
-
# @return [String] The encoded component.
-
#
-
# @example
-
# Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
-
# => "simple%2Fex%61mple"
-
# Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
-
# => "simple%2Fex%61mple"
-
# Addressable::URI.encode_component(
-
# "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
-
# )
-
# => "simple%2Fexample"
-
1
def self.encode_component(component, character_class=
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
-
upcase_encoded='')
-
309
return nil if component.nil?
-
-
begin
-
if component.kind_of?(Symbol) ||
-
component.kind_of?(Numeric) ||
-
component.kind_of?(TrueClass) ||
-
component.kind_of?(FalseClass)
-
component = component.to_s
-
else
-
component = component.to_str
-
end
-
rescue TypeError, NoMethodError
-
raise TypeError, "Can't convert #{component.class} into String."
-
309
end if !component.is_a? String
-
-
309
if ![String, Regexp].include?(character_class.class)
-
raise TypeError,
-
"Expected String or Regexp, got #{character_class.inspect}"
-
end
-
309
if character_class.kind_of?(String)
-
16
character_class = /[^#{character_class}]/
-
end
-
309
if component.respond_to?(:force_encoding)
-
# We can't perform regexps on invalid UTF sequences, but
-
# here we need to, so switch to ASCII.
-
309
component = component.dup
-
309
component.force_encoding(Encoding::ASCII_8BIT)
-
end
-
# Avoiding gsub! because there are edge cases with frozen strings
-
309
component = component.gsub(character_class) do |sequence|
-
(sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join
-
end
-
309
if upcase_encoded.length > 0
-
80
component = component.gsub(/%(#{upcase_encoded.chars.map do |char|
-
160
char.unpack('C*').map { |c| '%02x' % c }.join
-
end.join('|')})/i) { |s| s.upcase }
-
end
-
309
return component
-
end
-
-
1
class << self
-
1
alias_method :encode_component, :encode_component
-
end
-
-
##
-
# Unencodes any percent encoded characters within a URI component.
-
# This method may be used for unencoding either components or full URIs,
-
# however, it is recommended to use the <code>unencode_component</code>
-
# alias when unencoding components.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI or component to unencode.
-
#
-
# @param [Class] return_type
-
# The type of object to return.
-
# This value may only be set to <code>String</code> or
-
# <code>Addressable::URI</code>. All other values are invalid. Defaults
-
# to <code>String</code>.
-
#
-
# @param [String] leave_encoded
-
# A string of characters to leave encoded. If a percent encoded character
-
# in this list is encountered then it will remain percent encoded.
-
#
-
# @return [String, Addressable::URI]
-
# The unencoded component or URI.
-
# The return type is determined by the <code>return_type</code>
-
# parameter.
-
1
def self.unencode(uri, return_type=String, leave_encoded='')
-
368
return nil if uri.nil?
-
-
begin
-
uri = uri.to_str
-
rescue NoMethodError, TypeError
-
raise TypeError, "Can't convert #{uri.class} into String."
-
368
end if !uri.is_a? String
-
368
if ![String, ::Addressable::URI].include?(return_type)
-
raise TypeError,
-
"Expected Class (String or Addressable::URI), " +
-
"got #{return_type.inspect}"
-
end
-
368
uri = uri.dup
-
# Seriously, only use UTF-8. I'm really not kidding!
-
368
uri.force_encoding("utf-8") if uri.respond_to?(:force_encoding)
-
368
leave_encoded.force_encoding("utf-8") if leave_encoded.respond_to?(:force_encoding)
-
368
result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
-
c = sequence[1..3].to_i(16).chr
-
c.force_encoding("utf-8") if c.respond_to?(:force_encoding)
-
leave_encoded.include?(c) ? sequence : c
-
end
-
368
result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
-
368
if return_type == String
-
368
return result
-
elsif return_type == ::Addressable::URI
-
return ::Addressable::URI.parse(result)
-
end
-
end
-
-
1
class << self
-
1
alias_method :unescape, :unencode
-
1
alias_method :unencode_component, :unencode
-
1
alias_method :unescape_component, :unencode
-
end
-
-
-
##
-
# Normalizes the encoding of a URI component.
-
#
-
# @param [String, #to_str] component The URI component to encode.
-
#
-
# @param [String, Regexp] character_class
-
# The characters which are not percent encoded. If a <code>String</code>
-
# is passed, the <code>String</code> must be formatted as a regular
-
# expression character class. (Do not include the surrounding square
-
# brackets.) For example, <code>"b-zB-Z0-9"</code> would cause
-
# everything but the letters 'b' through 'z' and the numbers '0'
-
# through '9' to be percent encoded. If a <code>Regexp</code> is passed,
-
# the value <code>/[^b-zB-Z0-9]/</code> would have the same effect. A
-
# set of useful <code>String</code> values may be found in the
-
# <code>Addressable::URI::CharacterClasses</code> module. The default
-
# value is the reserved plus unreserved character classes specified in
-
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
-
#
-
# @param [String] leave_encoded
-
# When <code>character_class</code> is a <code>String</code> then
-
# <code>leave_encoded</code> is a string of characters that should remain
-
# percent encoded while normalizing the component; if they appear percent
-
# encoded in the original component, then they will be upcased ("%2f"
-
# normalized to "%2F") but otherwise left alone.
-
#
-
# @return [String] The normalized component.
-
#
-
# @example
-
# Addressable::URI.normalize_component("simpl%65/%65xampl%65", "b-zB-Z")
-
# => "simple%2Fex%61mple"
-
# Addressable::URI.normalize_component(
-
# "simpl%65/%65xampl%65", /[^b-zB-Z]/
-
# )
-
# => "simple%2Fex%61mple"
-
# Addressable::URI.normalize_component(
-
# "simpl%65/%65xampl%65",
-
# Addressable::URI::CharacterClasses::UNRESERVED
-
# )
-
# => "simple%2Fexample"
-
# Addressable::URI.normalize_component(
-
# "one%20two%2fthree%26four",
-
# "0-9a-zA-Z &/",
-
# "/"
-
# )
-
# => "one two%2Fthree&four"
-
1
def self.normalize_component(component, character_class=
-
CharacterClasses::RESERVED + CharacterClasses::UNRESERVED,
-
leave_encoded='')
-
293
return nil if component.nil?
-
-
begin
-
component = component.to_str
-
rescue NoMethodError, TypeError
-
raise TypeError, "Can't convert #{component.class} into String."
-
293
end if !component.is_a? String
-
-
293
if ![String, Regexp].include?(character_class.class)
-
raise TypeError,
-
"Expected String or Regexp, got #{character_class.inspect}"
-
end
-
293
if character_class.kind_of?(String)
-
293
leave_re = if leave_encoded.length > 0
-
80
character_class = "#{character_class}%" unless character_class.include?('%')
-
-
80
"|%(?!#{leave_encoded.chars.map do |char|
-
160
seq = char.unpack('C*').map { |c| '%02x' % c }.join
-
80
[seq.upcase, seq.downcase]
-
end.flatten.join('|')})"
-
end
-
-
293
character_class = /[^#{character_class}]#{leave_re}/
-
end
-
293
if component.respond_to?(:force_encoding)
-
# We can't perform regexps on invalid UTF sequences, but
-
# here we need to, so switch to ASCII.
-
293
component = component.dup
-
293
component.force_encoding(Encoding::ASCII_8BIT)
-
end
-
293
unencoded = self.unencode_component(component, String, leave_encoded)
-
293
begin
-
293
encoded = self.encode_component(
-
Addressable::IDNA.unicode_normalize_kc(unencoded),
-
character_class,
-
leave_encoded
-
)
-
rescue ArgumentError
-
encoded = self.encode_component(unencoded)
-
end
-
293
if encoded.respond_to?(:force_encoding)
-
293
encoded.force_encoding(Encoding::UTF_8)
-
end
-
293
return encoded
-
end
-
-
##
-
# Percent encodes any special characters in the URI.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI to encode.
-
#
-
# @param [Class] return_type
-
# The type of object to return.
-
# This value may only be set to <code>String</code> or
-
# <code>Addressable::URI</code>. All other values are invalid. Defaults
-
# to <code>String</code>.
-
#
-
# @return [String, Addressable::URI]
-
# The encoded URI.
-
# The return type is determined by the <code>return_type</code>
-
# parameter.
-
1
def self.encode(uri, return_type=String)
-
return nil if uri.nil?
-
-
begin
-
uri = uri.to_str
-
rescue NoMethodError, TypeError
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end if !uri.is_a? String
-
-
if ![String, ::Addressable::URI].include?(return_type)
-
raise TypeError,
-
"Expected Class (String or Addressable::URI), " +
-
"got #{return_type.inspect}"
-
end
-
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
-
encoded_uri = Addressable::URI.new(
-
:scheme => self.encode_component(uri_object.scheme,
-
Addressable::URI::CharacterClasses::SCHEME),
-
:authority => self.encode_component(uri_object.authority,
-
Addressable::URI::CharacterClasses::AUTHORITY),
-
:path => self.encode_component(uri_object.path,
-
Addressable::URI::CharacterClasses::PATH),
-
:query => self.encode_component(uri_object.query,
-
Addressable::URI::CharacterClasses::QUERY),
-
:fragment => self.encode_component(uri_object.fragment,
-
Addressable::URI::CharacterClasses::FRAGMENT)
-
)
-
if return_type == String
-
return encoded_uri.to_s
-
elsif return_type == ::Addressable::URI
-
return encoded_uri
-
end
-
end
-
-
1
class << self
-
1
alias_method :escape, :encode
-
end
-
-
##
-
# Normalizes the encoding of a URI. Characters within a hostname are
-
# not percent encoded to allow for internationalized domain names.
-
#
-
# @param [String, Addressable::URI, #to_str] uri
-
# The URI to encode.
-
#
-
# @param [Class] return_type
-
# The type of object to return.
-
# This value may only be set to <code>String</code> or
-
# <code>Addressable::URI</code>. All other values are invalid. Defaults
-
# to <code>String</code>.
-
#
-
# @return [String, Addressable::URI]
-
# The encoded URI.
-
# The return type is determined by the <code>return_type</code>
-
# parameter.
-
1
def self.normalized_encode(uri, return_type=String)
-
begin
-
uri = uri.to_str
-
rescue NoMethodError, TypeError
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end if !uri.is_a? String
-
-
if ![String, ::Addressable::URI].include?(return_type)
-
raise TypeError,
-
"Expected Class (String or Addressable::URI), " +
-
"got #{return_type.inspect}"
-
end
-
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
-
components = {
-
:scheme => self.unencode_component(uri_object.scheme),
-
:user => self.unencode_component(uri_object.user),
-
:password => self.unencode_component(uri_object.password),
-
:host => self.unencode_component(uri_object.host),
-
:port => (uri_object.port.nil? ? nil : uri_object.port.to_s),
-
:path => self.unencode_component(uri_object.path),
-
:query => self.unencode_component(uri_object.query),
-
:fragment => self.unencode_component(uri_object.fragment)
-
}
-
components.each do |key, value|
-
if value != nil
-
begin
-
components[key] =
-
Addressable::IDNA.unicode_normalize_kc(value.to_str)
-
rescue ArgumentError
-
# Likely a malformed UTF-8 character, skip unicode normalization
-
components[key] = value.to_str
-
end
-
end
-
end
-
encoded_uri = Addressable::URI.new(
-
:scheme => self.encode_component(components[:scheme],
-
Addressable::URI::CharacterClasses::SCHEME),
-
:user => self.encode_component(components[:user],
-
Addressable::URI::CharacterClasses::UNRESERVED),
-
:password => self.encode_component(components[:password],
-
Addressable::URI::CharacterClasses::UNRESERVED),
-
:host => components[:host],
-
:port => components[:port],
-
:path => self.encode_component(components[:path],
-
Addressable::URI::CharacterClasses::PATH),
-
:query => self.encode_component(components[:query],
-
Addressable::URI::CharacterClasses::QUERY),
-
:fragment => self.encode_component(components[:fragment],
-
Addressable::URI::CharacterClasses::FRAGMENT)
-
)
-
if return_type == String
-
return encoded_uri.to_s
-
elsif return_type == ::Addressable::URI
-
return encoded_uri
-
end
-
end
-
-
##
-
# Encodes a set of key/value pairs according to the rules for the
-
# <code>application/x-www-form-urlencoded</code> MIME type.
-
#
-
# @param [#to_hash, #to_ary] form_values
-
# The form values to encode.
-
#
-
# @param [TrueClass, FalseClass] sort
-
# Sort the key/value pairs prior to encoding.
-
# Defaults to <code>false</code>.
-
#
-
# @return [String]
-
# The encoded value.
-
1
def self.form_encode(form_values, sort=false)
-
if form_values.respond_to?(:to_hash)
-
form_values = form_values.to_hash.to_a
-
elsif form_values.respond_to?(:to_ary)
-
form_values = form_values.to_ary
-
else
-
raise TypeError, "Can't convert #{form_values.class} into Array."
-
end
-
-
form_values = form_values.inject([]) do |accu, (key, value)|
-
if value.kind_of?(Array)
-
value.each do |v|
-
accu << [key.to_s, v.to_s]
-
end
-
else
-
accu << [key.to_s, value.to_s]
-
end
-
accu
-
end
-
-
if sort
-
# Useful for OAuth and optimizing caching systems
-
form_values = form_values.sort
-
end
-
escaped_form_values = form_values.map do |(key, value)|
-
# Line breaks are CRLF pairs
-
[
-
self.encode_component(
-
key.gsub(/(\r\n|\n|\r)/, "\r\n"),
-
CharacterClasses::UNRESERVED
-
).gsub("%20", "+"),
-
self.encode_component(
-
value.gsub(/(\r\n|\n|\r)/, "\r\n"),
-
CharacterClasses::UNRESERVED
-
).gsub("%20", "+")
-
]
-
end
-
return (escaped_form_values.map do |(key, value)|
-
"#{key}=#{value}"
-
end).join("&")
-
end
-
-
##
-
# Decodes a <code>String</code> according to the rules for the
-
# <code>application/x-www-form-urlencoded</code> MIME type.
-
#
-
# @param [String, #to_str] encoded_value
-
# The form values to decode.
-
#
-
# @return [Array]
-
# The decoded values.
-
# This is not a <code>Hash</code> because of the possibility for
-
# duplicate keys.
-
1
def self.form_unencode(encoded_value)
-
if !encoded_value.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{encoded_value.class} into String."
-
end
-
encoded_value = encoded_value.to_str
-
split_values = encoded_value.split("&").map do |pair|
-
pair.split("=", 2)
-
end
-
return split_values.map do |(key, value)|
-
[
-
key ? self.unencode_component(
-
key.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n") : nil,
-
value ? (self.unencode_component(
-
value.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n")) : nil
-
]
-
end
-
end
-
-
##
-
# Creates a new uri object from component parts.
-
#
-
# @option [String, #to_str] scheme The scheme component.
-
# @option [String, #to_str] user The user component.
-
# @option [String, #to_str] password The password component.
-
# @option [String, #to_str] userinfo
-
# The userinfo component. If this is supplied, the user and password
-
# components must be omitted.
-
# @option [String, #to_str] host The host component.
-
# @option [String, #to_str] port The port component.
-
# @option [String, #to_str] authority
-
# The authority component. If this is supplied, the user, password,
-
# userinfo, host, and port components must be omitted.
-
# @option [String, #to_str] path The path component.
-
# @option [String, #to_str] query The query component.
-
# @option [String, #to_str] fragment The fragment component.
-
#
-
# @return [Addressable::URI] The constructed URI object.
-
1
def initialize(options={})
-
87
if options.has_key?(:authority)
-
40
if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
-
raise ArgumentError,
-
"Cannot specify both an authority and any of the components " +
-
"within the authority."
-
end
-
end
-
87
if options.has_key?(:userinfo)
-
if (options.keys & [:user, :password]).any?
-
raise ArgumentError,
-
"Cannot specify both a userinfo and either the user or password."
-
end
-
end
-
-
87
self.defer_validation do
-
# Bunch of crazy logic required because of the composite components
-
# like userinfo and authority.
-
87
self.scheme = options[:scheme] if options[:scheme]
-
87
self.user = options[:user] if options[:user]
-
87
self.password = options[:password] if options[:password]
-
87
self.userinfo = options[:userinfo] if options[:userinfo]
-
87
self.host = options[:host] if options[:host]
-
87
self.port = options[:port] if options[:port]
-
87
self.authority = options[:authority] if options[:authority]
-
87
self.path = options[:path] if options[:path]
-
87
self.query = options[:query] if options[:query]
-
87
self.query_values = options[:query_values] if options[:query_values]
-
87
self.fragment = options[:fragment] if options[:fragment]
-
end
-
end
-
-
##
-
# Freeze URI, initializing instance variables.
-
#
-
# @return [Addressable::URI] The frozen URI object.
-
1
def freeze
-
4
self.normalized_scheme
-
4
self.normalized_user
-
4
self.normalized_password
-
4
self.normalized_userinfo
-
4
self.normalized_host
-
4
self.normalized_port
-
4
self.normalized_authority
-
4
self.normalized_site
-
4
self.normalized_path
-
4
self.normalized_query
-
4
self.normalized_fragment
-
4
self.hash
-
4
super
-
end
-
-
##
-
# The scheme component for this URI.
-
#
-
# @return [String] The scheme component.
-
1
def scheme
-
824
return instance_variable_defined?(:@scheme) ? @scheme : nil
-
end
-
-
##
-
# The scheme component for this URI, normalized.
-
#
-
# @return [String] The scheme component, normalized.
-
1
def normalized_scheme
-
self.scheme && @normalized_scheme ||= (begin
-
41
if self.scheme =~ /^\s*ssh\+svn\s*$/i
-
"svn+ssh"
-
else
-
41
Addressable::URI.normalize_component(
-
self.scheme.strip.downcase,
-
Addressable::URI::CharacterClasses::SCHEME
-
)
-
end
-
137
end)
-
end
-
-
##
-
# Sets the scheme component for this URI.
-
#
-
# @param [String, #to_str] new_scheme The new scheme component.
-
1
def scheme=(new_scheme)
-
87
if new_scheme && !new_scheme.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_scheme.class} into String."
-
elsif new_scheme
-
87
new_scheme = new_scheme.to_str
-
end
-
87
if new_scheme && new_scheme !~ /[a-z][a-z0-9\.\+\-]*/i
-
raise InvalidURIError, "Invalid scheme format."
-
end
-
87
@scheme = new_scheme
-
87
@scheme = nil if @scheme.to_s.strip.empty?
-
-
# Reset dependant values
-
87
@normalized_scheme = nil
-
87
@uri_string = nil
-
87
@hash = nil
-
-
# Ensure we haven't created an invalid URI
-
87
validate()
-
end
-
-
##
-
# The user component for this URI.
-
#
-
# @return [String] The user component.
-
1
def user
-
165
return instance_variable_defined?(:@user) ? @user : nil
-
end
-
-
##
-
# The user component for this URI, normalized.
-
#
-
# @return [String] The user component, normalized.
-
1
def normalized_user
-
self.user && @normalized_user ||= (begin
-
if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
-
(!self.password || self.password.strip.empty?)
-
nil
-
else
-
Addressable::URI.normalize_component(
-
self.user.strip,
-
Addressable::URI::CharacterClasses::UNRESERVED
-
)
-
end
-
4
end)
-
end
-
-
##
-
# Sets the user component for this URI.
-
#
-
# @param [String, #to_str] new_user The new user component.
-
1
def user=(new_user)
-
40
if new_user && !new_user.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_user.class} into String."
-
end
-
40
@user = new_user ? new_user.to_str : nil
-
-
# You can't have a nil user with a non-nil password
-
40
if password != nil
-
@user = EMPTY_STR if @user.nil?
-
end
-
-
# Reset dependant values
-
40
@userinfo = nil
-
40
@normalized_userinfo = nil
-
40
@authority = nil
-
40
@normalized_user = nil
-
40
@uri_string = nil
-
40
@hash = nil
-
-
# Ensure we haven't created an invalid URI
-
40
validate()
-
end
-
-
##
-
# The password component for this URI.
-
#
-
# @return [String] The password component.
-
1
def password
-
205
return instance_variable_defined?(:@password) ? @password : nil
-
end
-
-
##
-
# The password component for this URI, normalized.
-
#
-
# @return [String] The password component, normalized.
-
1
def normalized_password
-
self.password && @normalized_password ||= (begin
-
if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
-
(!self.user || self.user.strip.empty?)
-
nil
-
else
-
Addressable::URI.normalize_component(
-
self.password.strip,
-
Addressable::URI::CharacterClasses::UNRESERVED
-
)
-
end
-
4
end)
-
end
-
-
##
-
# Sets the password component for this URI.
-
#
-
# @param [String, #to_str] new_password The new password component.
-
1
def password=(new_password)
-
40
if new_password && !new_password.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_password.class} into String."
-
end
-
40
@password = new_password ? new_password.to_str : nil
-
-
# You can't have a nil user with a non-nil password
-
40
@password ||= nil
-
40
@user ||= nil
-
40
if @password != nil
-
@user = EMPTY_STR if @user.nil?
-
end
-
-
# Reset dependant values
-
40
@userinfo = nil
-
40
@normalized_userinfo = nil
-
40
@authority = nil
-
40
@normalized_password = nil
-
40
@uri_string = nil
-
40
@hash = nil
-
-
# Ensure we haven't created an invalid URI
-
40
validate()
-
end
-
-
##
-
# The userinfo component for this URI.
-
# Combines the user and password components.
-
#
-
# @return [String] The userinfo component.
-
1
def userinfo
-
124
current_user = self.user
-
124
current_password = self.password
-
124
(current_user || current_password) && @userinfo ||= (begin
-
if current_user && current_password
-
"#{current_user}:#{current_password}"
-
elsif current_user && !current_password
-
"#{current_user}"
-
end
-
124
end)
-
end
-
-
##
-
# The userinfo component for this URI, normalized.
-
#
-
# @return [String] The userinfo component, normalized.
-
1
def normalized_userinfo
-
self.userinfo && @normalized_userinfo ||= (begin
-
current_user = self.normalized_user
-
current_password = self.normalized_password
-
if !current_user && !current_password
-
nil
-
elsif current_user && current_password
-
"#{current_user}:#{current_password}"
-
elsif current_user && !current_password
-
"#{current_user}"
-
end
-
45
end)
-
end
-
-
##
-
# Sets the userinfo component for this URI.
-
#
-
# @param [String, #to_str] new_userinfo The new userinfo component.
-
1
def userinfo=(new_userinfo)
-
if new_userinfo && !new_userinfo.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_userinfo.class} into String."
-
end
-
new_user, new_password = if new_userinfo
-
[
-
new_userinfo.to_str.strip[/^(.*):/, 1],
-
new_userinfo.to_str.strip[/:(.*)$/, 1]
-
]
-
else
-
[nil, nil]
-
end
-
-
# Password assigned first to ensure validity in case of nil
-
self.password = new_password
-
self.user = new_user
-
-
# Reset dependant values
-
@authority = nil
-
@uri_string = nil
-
@hash = nil
-
-
# Ensure we haven't created an invalid URI
-
validate()
-
end
-
-
##
-
# The host component for this URI.
-
#
-
# @return [String] The host component.
-
1
def host
-
727
return instance_variable_defined?(:@host) ? @host : nil
-
end
-
-
##
-
# The host component for this URI, normalized.
-
#
-
# @return [String] The host component, normalized.
-
1
def normalized_host
-
self.host && @normalized_host ||= (begin
-
41
if !self.host.strip.empty?
-
41
result = ::Addressable::IDNA.to_ascii(
-
URI.unencode_component(self.host.strip.downcase)
-
)
-
41
if result =~ /[^\.]\.$/
-
# Single trailing dots are unnecessary.
-
result = result[0...-1]
-
end
-
41
result
-
else
-
EMPTY_STR
-
end
-
45
end)
-
end
-
-
##
-
# Sets the host component for this URI.
-
#
-
# @param [String, #to_str] new_host The new host component.
-
1
def host=(new_host)
-
87
if new_host && !new_host.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_host.class} into String."
-
end
-
87
@host = new_host ? new_host.to_str : nil
-
-
87
unreserved = CharacterClasses::UNRESERVED
-
87
sub_delims = CharacterClasses::SUB_DELIMS
-
if @host != nil && (@host =~ /[<>{}\/\?\#\@]/ ||
-
(@host[/^\[(.*)\]$/, 1] != nil && @host[/^\[(.*)\]$/, 1] !~
-
87
Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
-
raise InvalidURIError, "Invalid character in host: '#{@host.to_s}'"
-
end
-
-
# Reset dependant values
-
87
@authority = nil
-
87
@normalized_host = nil
-
87
@uri_string = nil
-
87
@hash = nil
-
-
# Ensure we haven't created an invalid URI
-
87
validate()
-
end
-
-
##
-
# This method is same as URI::Generic#host except
-
# brackets for IPv6 (and 'IPvFuture') addresses are removed.
-
#
-
# @see Addressable::URI#host
-
#
-
# @return [String] The hostname for this URI.
-
1
def hostname
-
v = self.host
-
/\A\[(.*)\]\z/ =~ v ? $1 : v
-
end
-
-
##
-
# This method is same as URI::Generic#host= except
-
# the argument can be a bare IPv6 address (or 'IPvFuture').
-
#
-
# @see Addressable::URI#host=
-
#
-
# @param [String, #to_str] new_hostname The new hostname for this URI.
-
1
def hostname=(new_hostname)
-
if new_hostname && !new_hostname.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_hostname.class} into String."
-
end
-
v = new_hostname ? new_hostname.to_str : nil
-
v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v
-
self.host = v
-
end
-
-
##
-
# The authority component for this URI.
-
# Combines the user, password, host, and port components.
-
#
-
# @return [String] The authority component.
-
1
def authority
-
self.host && @authority ||= (begin
-
79
authority = ""
-
79
if self.userinfo != nil
-
authority << "#{self.userinfo}@"
-
end
-
79
authority << self.host
-
79
if self.port != nil
-
35
authority << ":#{self.port}"
-
end
-
79
authority
-
174
end)
-
end
-
-
##
-
# The authority component for this URI, normalized.
-
#
-
# @return [String] The authority component, normalized.
-
1
def normalized_authority
-
self.authority && @normalized_authority ||= (begin
-
41
authority = ""
-
41
if self.normalized_userinfo != nil
-
authority << "#{self.normalized_userinfo}@"
-
end
-
41
authority << self.normalized_host
-
41
if self.normalized_port != nil
-
authority << ":#{self.normalized_port}"
-
end
-
41
authority
-
52
end)
-
end
-
-
##
-
# Sets the authority component for this URI.
-
#
-
# @param [String, #to_str] new_authority The new authority component.
-
1
def authority=(new_authority)
-
40
if new_authority
-
40
if !new_authority.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_authority.class} into String."
-
end
-
40
new_authority = new_authority.to_str
-
40
new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
-
40
if new_userinfo
-
new_user = new_userinfo.strip[/^([^:]*):?/, 1]
-
new_password = new_userinfo.strip[/:(.*)$/, 1]
-
end
-
40
new_host = new_authority.gsub(
-
/^([^\[\]]*)@/, EMPTY_STR
-
).gsub(
-
/:([^:@\[\]]*?)$/, EMPTY_STR
-
)
-
40
new_port =
-
new_authority[/:([^:@\[\]]*?)$/, 1]
-
end
-
-
# Password assigned first to ensure validity in case of nil
-
40
self.password = defined?(new_password) ? new_password : nil
-
40
self.user = defined?(new_user) ? new_user : nil
-
40
self.host = defined?(new_host) ? new_host : nil
-
40
self.port = defined?(new_port) ? new_port : nil
-
-
# Reset dependant values
-
40
@userinfo = nil
-
40
@normalized_userinfo = nil
-
40
@uri_string = nil
-
40
@hash = nil
-
-
# Ensure we haven't created an invalid URI
-
40
validate()
-
end
-
-
##
-
# The origin for this URI, serialized to ASCII, as per
-
# RFC 6454, section 6.2.
-
#
-
# @return [String] The serialized origin.
-
1
def origin
-
return (if self.scheme && self.authority
-
if self.normalized_port
-
(
-
"#{self.normalized_scheme}://#{self.normalized_host}" +
-
":#{self.normalized_port}"
-
)
-
else
-
"#{self.normalized_scheme}://#{self.normalized_host}"
-
end
-
else
-
"null"
-
end)
-
end
-
-
# Returns an array of known ip-based schemes. These schemes typically
-
# use a similar URI form:
-
# <code>//<user>:<password>@<host>:<port>/<url-path></code>
-
1
def self.ip_based_schemes
-
91
return self.port_mapping.keys
-
end
-
-
# Returns a hash of common IP-based schemes and their default port
-
# numbers. Adding new schemes to this hash, as necessary, will allow
-
# for better URI normalization.
-
1
def self.port_mapping
-
140
PORT_MAPPING
-
end
-
-
##
-
# The port component for this URI.
-
# This is the port number actually given in the URI. This does not
-
# infer port numbers from default values.
-
#
-
# @return [Integer] The port component.
-
1
def port
-
214
return instance_variable_defined?(:@port) ? @port : nil
-
end
-
-
##
-
# The port component for this URI, normalized.
-
#
-
# @return [Integer] The port component, normalized.
-
1
def normalized_port
-
45
if URI.port_mapping[self.normalized_scheme] == self.port
-
nil
-
else
-
10
self.port
-
end
-
end
-
-
##
-
# Sets the port component for this URI.
-
#
-
# @param [String, Integer, #to_s] new_port The new port component.
-
1
def port=(new_port)
-
81
if new_port != nil && new_port.respond_to?(:to_str)
-
2
new_port = Addressable::URI.unencode_component(new_port.to_str)
-
end
-
81
if new_port != nil && !(new_port.to_s =~ /^\d+$/)
-
raise InvalidURIError,
-
"Invalid port number: #{new_port.inspect}"
-
end
-
-
81
@port = new_port.to_s.to_i
-
81
@port = nil if @port == 0
-
-
# Reset dependant values
-
81
@authority = nil
-
81
@normalized_port = nil
-
81
@uri_string = nil
-
81
@hash = nil
-
-
# Ensure we haven't created an invalid URI
-
81
validate()
-
end
-
-
##
-
# The inferred port component for this URI.
-
# This method will normalize to the default port for the URI's scheme if
-
# the port isn't explicitly specified in the URI.
-
#
-
# @return [Integer] The inferred port component.
-
1
def inferred_port
-
4
if self.port.to_i == 0
-
4
self.default_port
-
else
-
self.port.to_i
-
end
-
end
-
-
##
-
# The default port for this URI's scheme.
-
# This method will always returns the default port for the URI's scheme
-
# regardless of the presence of an explicit port in the URI.
-
#
-
# @return [Integer] The default port.
-
1
def default_port
-
4
URI.port_mapping[self.scheme.strip.downcase] if self.scheme
-
end
-
-
##
-
# The combination of components that represent a site.
-
# Combines the scheme, user, password, host, and port components.
-
# Primarily useful for HTTP and HTTPS.
-
#
-
# For example, <code>"http://example.com/path?query"</code> would have a
-
# <code>site</code> value of <code>"http://example.com"</code>.
-
#
-
# @return [String] The components that identify a site.
-
1
def site
-
4
(self.scheme || self.authority) && @site ||= (begin
-
4
site_string = ""
-
4
site_string << "#{self.scheme}:" if self.scheme != nil
-
4
site_string << "//#{self.authority}" if self.authority != nil
-
4
site_string
-
4
end)
-
end
-
-
##
-
# The normalized combination of components that represent a site.
-
# Combines the scheme, user, password, host, and port components.
-
# Primarily useful for HTTP and HTTPS.
-
#
-
# For example, <code>"http://example.com/path?query"</code> would have a
-
# <code>site</code> value of <code>"http://example.com"</code>.
-
#
-
# @return [String] The normalized components that identify a site.
-
1
def normalized_site
-
self.site && @normalized_site ||= (begin
-
4
site_string = ""
-
4
if self.normalized_scheme != nil
-
4
site_string << "#{self.normalized_scheme}:"
-
end
-
4
if self.normalized_authority != nil
-
4
site_string << "//#{self.normalized_authority}"
-
end
-
4
site_string
-
4
end)
-
end
-
-
##
-
# Sets the site value for this URI.
-
#
-
# @param [String, #to_str] new_site The new site value.
-
1
def site=(new_site)
-
if new_site
-
if !new_site.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_site.class} into String."
-
end
-
new_site = new_site.to_str
-
# These two regular expressions derived from the primary parsing
-
# expression
-
self.scheme = new_site[/^(?:([^:\/?#]+):)?(?:\/\/(?:[^\/?#]*))?$/, 1]
-
self.authority = new_site[
-
/^(?:(?:[^:\/?#]+):)?(?:\/\/([^\/?#]*))?$/, 1
-
]
-
else
-
self.scheme = nil
-
self.authority = nil
-
end
-
end
-
-
##
-
# The path component for this URI.
-
#
-
# @return [String] The path component.
-
1
def path
-
449
return instance_variable_defined?(:@path) ? @path : EMPTY_STR
-
end
-
-
1
NORMPATH = /^(?!\/)[^\/:]*:.*$/
-
##
-
# The path component for this URI, normalized.
-
#
-
# @return [String] The path component, normalized.
-
1
def normalized_path
-
@normalized_path ||= (begin
-
41
path = self.path.to_s
-
41
if self.scheme == nil && path =~ NORMPATH
-
# Relative paths with colons in the first segment are ambiguous.
-
path = path.sub(":", "%2F")
-
end
-
# String#split(delimeter, -1) uses the more strict splitting behavior
-
# found by default in Python.
-
41
result = (path.strip.split(SLASH, -1).map do |segment|
-
172
Addressable::URI.normalize_component(
-
segment,
-
Addressable::URI::CharacterClasses::PCHAR
-
)
-
end).join(SLASH)
-
-
41
result = URI.normalize_path(result)
-
if result.empty? &&
-
41
["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
-
result = SLASH
-
end
-
41
result
-
44
end)
-
end
-
-
##
-
# Sets the path component for this URI.
-
#
-
# @param [String, #to_str] new_path The new path component.
-
1
def path=(new_path)
-
87
if new_path && !new_path.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_path.class} into String."
-
end
-
87
@path = (new_path || EMPTY_STR).to_str
-
87
if !@path.empty? && @path[0..0] != SLASH && host != nil
-
@path = "/#{@path}"
-
end
-
-
# Reset dependant values
-
87
@normalized_path = nil
-
87
@uri_string = nil
-
87
@hash = nil
-
end
-
-
##
-
# The basename, if any, of the file in the path component.
-
#
-
# @return [String] The path's basename.
-
1
def basename
-
# Path cannot be nil
-
return File.basename(self.path).gsub(/;[^\/]*$/, EMPTY_STR)
-
end
-
-
##
-
# The extname, if any, of the file in the path component.
-
# Empty string if there is no extension.
-
#
-
# @return [String] The path's extname.
-
1
def extname
-
return nil unless self.path
-
return File.extname(self.basename)
-
end
-
-
##
-
# The query component for this URI.
-
#
-
# @return [String] The query component.
-
1
def query
-
246
return instance_variable_defined?(:@query) ? @query : nil
-
end
-
-
##
-
# The query component for this URI, normalized.
-
#
-
# @return [String] The query component, normalized.
-
1
def normalized_query(*flags)
-
44
modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
-
# Make sure possible key-value pair delimiters are escaped.
-
44
modified_query_class.sub!("\\&", "").sub!("\\;", "")
-
44
pairs = (self.query || "").split("&", -1)
-
44
pairs.sort! if flags.include?(:sorted)
-
44
component = (pairs.map do |pair|
-
80
Addressable::URI.normalize_component(pair, modified_query_class, "+")
-
end).join("&")
-
44
component == "" ? nil : component
-
end
-
-
##
-
# Sets the query component for this URI.
-
#
-
# @param [String, #to_str] new_query The new query component.
-
1
def query=(new_query)
-
89
if new_query && !new_query.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_query.class} into String."
-
end
-
89
@query = new_query ? new_query.to_str : nil
-
-
# Reset dependant values
-
89
@normalized_query = nil
-
89
@uri_string = nil
-
89
@hash = nil
-
end
-
-
##
-
# Converts the query component to a Hash value.
-
#
-
# @param [Class] return_type The return type desired. Value must be either
-
# `Hash` or `Array`.
-
#
-
# @return [Hash, Array] The query string parsed as a Hash or Array object.
-
#
-
# @example
-
# Addressable::URI.parse("?one=1&two=2&three=3").query_values
-
# #=> {"one" => "1", "two" => "2", "three" => "3"}
-
# Addressable::URI.parse("?one=two&one=three").query_values(Array)
-
# #=> [["one", "two"], ["one", "three"]]
-
# Addressable::URI.parse("?one=two&one=three").query_values(Hash)
-
# #=> {"one" => "three"}
-
1
def query_values(return_type=Hash)
-
4
empty_accumulator = Array == return_type ? [] : {}
-
4
if return_type != Hash && return_type != Array
-
raise ArgumentError, "Invalid return type. Must be Hash or Array."
-
end
-
4
return nil if self.query == nil
-
4
split_query = (self.query.split("&").map do |pair|
-
8
pair.split("=", 2) if pair && !pair.empty?
-
end).compact
-
4
return split_query.inject(empty_accumulator.dup) do |accu, pair|
-
# I'd rather use key/value identifiers instead of array lookups,
-
# but in this case I really want to maintain the exact pair structure,
-
# so it's best to make all changes in-place.
-
8
pair[0] = URI.unencode_component(pair[0])
-
8
if pair[1].respond_to?(:to_str)
-
# I loathe the fact that I have to do this. Stupid HTML 4.01.
-
# Treating '+' as a space was just an unbelievably bad idea.
-
# There was nothing wrong with '%20'!
-
# If it ain't broke, don't fix it!
-
8
pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " "))
-
end
-
8
if return_type == Hash
-
8
accu[pair[0]] = pair[1]
-
else
-
accu << pair
-
end
-
8
accu
-
end
-
end
-
-
##
-
# Sets the query component for this URI from a Hash object.
-
# An empty Hash or Array will result in an empty query string.
-
#
-
# @param [Hash, #to_hash, Array] new_query_values The new query values.
-
#
-
# @example
-
# uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
-
# uri.query
-
# # => "a=a&b=c&b=d&b=e"
-
# uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
-
# uri.query
-
# # => "a=a&b=c&b=d&b=e"
-
# uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
-
# uri.query
-
# # => "a=a&b=c&b=d&b=e"
-
# uri.query_values = [['flag'], ['key', 'value']]
-
# uri.query
-
# # => "flag&key=value"
-
1
def query_values=(new_query_values)
-
if new_query_values == nil
-
self.query = nil
-
return nil
-
end
-
-
if !new_query_values.is_a?(Array)
-
if !new_query_values.respond_to?(:to_hash)
-
raise TypeError,
-
"Can't convert #{new_query_values.class} into Hash."
-
end
-
new_query_values = new_query_values.to_hash
-
new_query_values = new_query_values.map do |key, value|
-
key = key.to_s if key.kind_of?(Symbol)
-
[key, value]
-
end
-
# Useful default for OAuth and caching.
-
# Only to be used for non-Array inputs. Arrays should preserve order.
-
new_query_values.sort!
-
end
-
-
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
-
buffer = ""
-
new_query_values.each do |key, value|
-
encoded_key = URI.encode_component(
-
key, CharacterClasses::UNRESERVED
-
)
-
if value == nil
-
buffer << "#{encoded_key}&"
-
elsif value.kind_of?(Array)
-
value.each do |sub_value|
-
encoded_value = URI.encode_component(
-
sub_value, CharacterClasses::UNRESERVED
-
)
-
buffer << "#{encoded_key}=#{encoded_value}&"
-
end
-
else
-
encoded_value = URI.encode_component(
-
value, CharacterClasses::UNRESERVED
-
)
-
buffer << "#{encoded_key}=#{encoded_value}&"
-
end
-
end
-
self.query = buffer.chop
-
end
-
-
##
-
# The HTTP request URI for this URI. This is the path and the
-
# query string.
-
#
-
# @return [String] The request URI required for an HTTP request.
-
1
def request_uri
-
return nil if self.absolute? && self.scheme !~ /^https?$/
-
return (
-
(!self.path.empty? ? self.path : SLASH) +
-
(self.query ? "?#{self.query}" : EMPTY_STR)
-
)
-
end
-
-
##
-
# Sets the HTTP request URI for this URI.
-
#
-
# @param [String, #to_str] new_request_uri The new HTTP request URI.
-
1
def request_uri=(new_request_uri)
-
if !new_request_uri.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_request_uri.class} into String."
-
end
-
if self.absolute? && self.scheme !~ /^https?$/
-
raise InvalidURIError,
-
"Cannot set an HTTP request URI for a non-HTTP URI."
-
end
-
new_request_uri = new_request_uri.to_str
-
path_component = new_request_uri[/^([^\?]*)\?(?:.*)$/, 1]
-
query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1]
-
path_component = path_component.to_s
-
path_component = (!path_component.empty? ? path_component : SLASH)
-
self.path = path_component
-
self.query = query_component
-
-
# Reset dependant values
-
@uri_string = nil
-
@hash = nil
-
end
-
-
##
-
# The fragment component for this URI.
-
#
-
# @return [String] The fragment component.
-
1
def fragment
-
138
return instance_variable_defined?(:@fragment) ? @fragment : nil
-
end
-
-
##
-
# The fragment component for this URI, normalized.
-
#
-
# @return [String] The fragment component, normalized.
-
1
def normalized_fragment
-
self.fragment && @normalized_fragment ||= (begin
-
component = Addressable::URI.normalize_component(
-
self.fragment,
-
Addressable::URI::CharacterClasses::FRAGMENT
-
)
-
component == "" ? nil : component
-
44
end)
-
end
-
-
##
-
# Sets the fragment component for this URI.
-
#
-
# @param [String, #to_str] new_fragment The new fragment component.
-
1
def fragment=(new_fragment)
-
if new_fragment && !new_fragment.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{new_fragment.class} into String."
-
end
-
@fragment = new_fragment ? new_fragment.to_str : nil
-
-
# Reset dependant values
-
@normalized_fragment = nil
-
@uri_string = nil
-
@hash = nil
-
-
# Ensure we haven't created an invalid URI
-
validate()
-
end
-
-
##
-
# Determines if the scheme indicates an IP-based protocol.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the scheme indicates an IP-based protocol.
-
# <code>false</code> otherwise.
-
1
def ip_based?
-
91
if self.scheme
-
return URI.ip_based_schemes.include?(
-
91
self.scheme.strip.downcase)
-
end
-
return false
-
end
-
-
##
-
# Determines if the URI is relative.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URI is relative. <code>false</code>
-
# otherwise.
-
1
def relative?
-
return self.scheme.nil?
-
end
-
-
##
-
# Determines if the URI is absolute.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URI is absolute. <code>false</code>
-
# otherwise.
-
1
def absolute?
-
return !relative?
-
end
-
-
##
-
# Joins two URIs together.
-
#
-
# @param [String, Addressable::URI, #to_str] The URI to join with.
-
#
-
# @return [Addressable::URI] The joined URI.
-
1
def join(uri)
-
if !uri.respond_to?(:to_str)
-
raise TypeError, "Can't convert #{uri.class} into String."
-
end
-
if !uri.kind_of?(URI)
-
# Otherwise, convert to a String, then parse.
-
uri = URI.parse(uri.to_str)
-
end
-
if uri.to_s.empty?
-
return self.dup
-
end
-
-
joined_scheme = nil
-
joined_user = nil
-
joined_password = nil
-
joined_host = nil
-
joined_port = nil
-
joined_path = nil
-
joined_query = nil
-
joined_fragment = nil
-
-
# Section 5.2.2 of RFC 3986
-
if uri.scheme != nil
-
joined_scheme = uri.scheme
-
joined_user = uri.user
-
joined_password = uri.password
-
joined_host = uri.host
-
joined_port = uri.port
-
joined_path = URI.normalize_path(uri.path)
-
joined_query = uri.query
-
else
-
if uri.authority != nil
-
joined_user = uri.user
-
joined_password = uri.password
-
joined_host = uri.host
-
joined_port = uri.port
-
joined_path = URI.normalize_path(uri.path)
-
joined_query = uri.query
-
else
-
if uri.path == nil || uri.path.empty?
-
joined_path = self.path
-
if uri.query != nil
-
joined_query = uri.query
-
else
-
joined_query = self.query
-
end
-
else
-
if uri.path[0..0] == SLASH
-
joined_path = URI.normalize_path(uri.path)
-
else
-
base_path = self.path.dup
-
base_path = EMPTY_STR if base_path == nil
-
base_path = URI.normalize_path(base_path)
-
-
# Section 5.2.3 of RFC 3986
-
#
-
# Removes the right-most path segment from the base path.
-
if base_path =~ /\//
-
base_path.gsub!(/\/[^\/]+$/, SLASH)
-
else
-
base_path = EMPTY_STR
-
end
-
-
# If the base path is empty and an authority segment has been
-
# defined, use a base path of SLASH
-
if base_path.empty? && self.authority != nil
-
base_path = SLASH
-
end
-
-
joined_path = URI.normalize_path(base_path + uri.path)
-
end
-
joined_query = uri.query
-
end
-
joined_user = self.user
-
joined_password = self.password
-
joined_host = self.host
-
joined_port = self.port
-
end
-
joined_scheme = self.scheme
-
end
-
joined_fragment = uri.fragment
-
-
return self.class.new(
-
:scheme => joined_scheme,
-
:user => joined_user,
-
:password => joined_password,
-
:host => joined_host,
-
:port => joined_port,
-
:path => joined_path,
-
:query => joined_query,
-
:fragment => joined_fragment
-
)
-
end
-
1
alias_method :+, :join
-
-
##
-
# Destructive form of <code>join</code>.
-
#
-
# @param [String, Addressable::URI, #to_str] The URI to join with.
-
#
-
# @return [Addressable::URI] The joined URI.
-
#
-
# @see Addressable::URI#join
-
1
def join!(uri)
-
replace_self(self.join(uri))
-
end
-
-
##
-
# Merges a URI with a <code>Hash</code> of components.
-
# This method has different behavior from <code>join</code>. Any
-
# components present in the <code>hash</code> parameter will override the
-
# original components. The path component is not treated specially.
-
#
-
# @param [Hash, Addressable::URI, #to_hash] The components to merge with.
-
#
-
# @return [Addressable::URI] The merged URI.
-
#
-
# @see Hash#merge
-
1
def merge(hash)
-
if !hash.respond_to?(:to_hash)
-
raise TypeError, "Can't convert #{hash.class} into Hash."
-
end
-
hash = hash.to_hash
-
-
if hash.has_key?(:authority)
-
if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
-
raise ArgumentError,
-
"Cannot specify both an authority and any of the components " +
-
"within the authority."
-
end
-
end
-
if hash.has_key?(:userinfo)
-
if (hash.keys & [:user, :password]).any?
-
raise ArgumentError,
-
"Cannot specify both a userinfo and either the user or password."
-
end
-
end
-
-
uri = self.class.new
-
uri.defer_validation do
-
# Bunch of crazy logic required because of the composite components
-
# like userinfo and authority.
-
uri.scheme =
-
hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
-
if hash.has_key?(:authority)
-
uri.authority =
-
hash.has_key?(:authority) ? hash[:authority] : self.authority
-
end
-
if hash.has_key?(:userinfo)
-
uri.userinfo =
-
hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
-
end
-
if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
-
uri.user =
-
hash.has_key?(:user) ? hash[:user] : self.user
-
uri.password =
-
hash.has_key?(:password) ? hash[:password] : self.password
-
end
-
if !hash.has_key?(:authority)
-
uri.host =
-
hash.has_key?(:host) ? hash[:host] : self.host
-
uri.port =
-
hash.has_key?(:port) ? hash[:port] : self.port
-
end
-
uri.path =
-
hash.has_key?(:path) ? hash[:path] : self.path
-
uri.query =
-
hash.has_key?(:query) ? hash[:query] : self.query
-
uri.fragment =
-
hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
-
end
-
-
return uri
-
end
-
-
##
-
# Destructive form of <code>merge</code>.
-
#
-
# @param [Hash, Addressable::URI, #to_hash] The components to merge with.
-
#
-
# @return [Addressable::URI] The merged URI.
-
#
-
# @see Addressable::URI#merge
-
1
def merge!(uri)
-
replace_self(self.merge(uri))
-
end
-
-
##
-
# Returns the shortest normalized relative form of this URI that uses the
-
# supplied URI as a base for resolution. Returns an absolute URI if
-
# necessary. This is effectively the opposite of <code>route_to</code>.
-
#
-
# @param [String, Addressable::URI, #to_str] uri The URI to route from.
-
#
-
# @return [Addressable::URI]
-
# The normalized relative URI that is equivalent to the original URI.
-
1
def route_from(uri)
-
uri = URI.parse(uri).normalize
-
normalized_self = self.normalize
-
if normalized_self.relative?
-
raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
-
end
-
if uri.relative?
-
raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
-
end
-
if normalized_self == uri
-
return Addressable::URI.parse("##{normalized_self.fragment}")
-
end
-
components = normalized_self.to_hash
-
if normalized_self.scheme == uri.scheme
-
components[:scheme] = nil
-
if normalized_self.authority == uri.authority
-
components[:user] = nil
-
components[:password] = nil
-
components[:host] = nil
-
components[:port] = nil
-
if normalized_self.path == uri.path
-
components[:path] = nil
-
if normalized_self.query == uri.query
-
components[:query] = nil
-
end
-
else
-
if uri.path != SLASH and components[:path]
-
self_splitted_path = split_path(components[:path])
-
uri_splitted_path = split_path(uri.path)
-
self_dir = self_splitted_path.shift
-
uri_dir = uri_splitted_path.shift
-
while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir
-
self_dir = self_splitted_path.shift
-
uri_dir = uri_splitted_path.shift
-
end
-
components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH)
-
end
-
end
-
end
-
end
-
# Avoid network-path references.
-
if components[:host] != nil
-
components[:scheme] = normalized_self.scheme
-
end
-
return Addressable::URI.new(
-
:scheme => components[:scheme],
-
:user => components[:user],
-
:password => components[:password],
-
:host => components[:host],
-
:port => components[:port],
-
:path => components[:path],
-
:query => components[:query],
-
:fragment => components[:fragment]
-
)
-
end
-
-
##
-
# Returns the shortest normalized relative form of the supplied URI that
-
# uses this URI as a base for resolution. Returns an absolute URI if
-
# necessary. This is effectively the opposite of <code>route_from</code>.
-
#
-
# @param [String, Addressable::URI, #to_str] uri The URI to route to.
-
#
-
# @return [Addressable::URI]
-
# The normalized relative URI that is equivalent to the supplied URI.
-
1
def route_to(uri)
-
return URI.parse(uri).route_from(self)
-
end
-
-
##
-
# Returns a normalized URI object.
-
#
-
# NOTE: This method does not attempt to fully conform to specifications.
-
# It exists largely to correct other people's failures to read the
-
# specifications, and also to deal with caching issues since several
-
# different URIs may represent the same resource and should not be
-
# cached multiple times.
-
#
-
# @return [Addressable::URI] The normalized URI.
-
1
def normalize
-
# This is a special exception for the frequently misused feed
-
# URI scheme.
-
40
if normalized_scheme == "feed"
-
if self.to_s =~ /^feed:\/*http:\/*/
-
return URI.parse(
-
self.to_s[/^feed:\/*(http:\/*.*)/, 1]
-
).normalize
-
end
-
end
-
-
40
return self.class.new(
-
:scheme => normalized_scheme,
-
:authority => normalized_authority,
-
:path => normalized_path,
-
:query => normalized_query,
-
:fragment => normalized_fragment
-
)
-
end
-
-
##
-
# Destructively normalizes this URI object.
-
#
-
# @return [Addressable::URI] The normalized URI.
-
#
-
# @see Addressable::URI#normalize
-
1
def normalize!
-
replace_self(self.normalize)
-
end
-
-
##
-
# Creates a URI suitable for display to users. If semantic attacks are
-
# likely, the application should try to detect these and warn the user.
-
# See <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
-
# section 7.6 for more information.
-
#
-
# @return [Addressable::URI] A URI suitable for display purposes.
-
1
def display_uri
-
display_uri = self.normalize
-
display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
-
return display_uri
-
end
-
-
##
-
# Returns <code>true</code> if the URI objects are equal. This method
-
# normalizes both URIs before doing the comparison, and allows comparison
-
# against <code>Strings</code>.
-
#
-
# @param [Object] uri The URI to compare.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URIs are equivalent, <code>false</code>
-
# otherwise.
-
1
def ===(uri)
-
18
if uri.respond_to?(:normalize)
-
18
uri_string = uri.normalize.to_s
-
else
-
begin
-
uri_string = ::Addressable::URI.parse(uri).normalize.to_s
-
rescue InvalidURIError, TypeError
-
return false
-
end
-
end
-
18
return self.normalize.to_s == uri_string
-
end
-
-
##
-
# Returns <code>true</code> if the URI objects are equal. This method
-
# normalizes both URIs before doing the comparison.
-
#
-
# @param [Object] uri The URI to compare.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URIs are equivalent, <code>false</code>
-
# otherwise.
-
1
def ==(uri)
-
return false unless uri.kind_of?(URI)
-
return self.normalize.to_s == uri.normalize.to_s
-
end
-
-
##
-
# Returns <code>true</code> if the URI objects are equal. This method
-
# does NOT normalize either URI before doing the comparison.
-
#
-
# @param [Object] uri The URI to compare.
-
#
-
# @return [TrueClass, FalseClass]
-
# <code>true</code> if the URIs are equivalent, <code>false</code>
-
# otherwise.
-
1
def eql?(uri)
-
return false unless uri.kind_of?(URI)
-
return self.to_s == uri.to_s
-
end
-
-
##
-
# A hash value that will make a URI equivalent to its normalized
-
# form.
-
#
-
# @return [Integer] A hash of the URI.
-
1
def hash
-
4
return @hash ||= (self.to_s.hash * -1)
-
end
-
-
##
-
# Clones the URI object.
-
#
-
# @return [Addressable::URI] The cloned URI.
-
1
def dup
-
37
duplicated_uri = self.class.new(
-
:scheme => self.scheme ? self.scheme.dup : nil,
-
:user => self.user ? self.user.dup : nil,
-
:password => self.password ? self.password.dup : nil,
-
:host => self.host ? self.host.dup : nil,
-
:port => self.port,
-
:path => self.path ? self.path.dup : nil,
-
:query => self.query ? self.query.dup : nil,
-
:fragment => self.fragment ? self.fragment.dup : nil
-
)
-
37
return duplicated_uri
-
end
-
-
##
-
# Omits components from a URI.
-
#
-
# @param [Symbol] *components The components to be omitted.
-
#
-
# @return [Addressable::URI] The URI with components omitted.
-
#
-
# @example
-
# uri = Addressable::URI.parse("http://example.com/path?query")
-
# #=> #<Addressable::URI:0xcc5e7a URI:http://example.com/path?query>
-
# uri.omit(:scheme, :authority)
-
# #=> #<Addressable::URI:0xcc4d86 URI:/path?query>
-
1
def omit(*components)
-
invalid_components = components - [
-
:scheme, :user, :password, :userinfo, :host, :port, :authority,
-
:path, :query, :fragment
-
]
-
unless invalid_components.empty?
-
raise ArgumentError,
-
"Invalid component names: #{invalid_components.inspect}."
-
end
-
duplicated_uri = self.dup
-
duplicated_uri.defer_validation do
-
components.each do |component|
-
duplicated_uri.send((component.to_s + "=").to_sym, nil)
-
end
-
duplicated_uri.user = duplicated_uri.normalized_user
-
end
-
duplicated_uri
-
end
-
-
##
-
# Destructive form of omit.
-
#
-
# @param [Symbol] *components The components to be omitted.
-
#
-
# @return [Addressable::URI] The URI with components omitted.
-
#
-
# @see Addressable::URI#omit
-
1
def omit!(*components)
-
replace_self(self.omit(*components))
-
end
-
-
##
-
# Determines if the URI is an empty string.
-
#
-
# @return [TrueClass, FalseClass]
-
# Returns <code>true</code> if empty, <code>false</code> otherwise.
-
1
def empty?
-
return self.to_s.empty?
-
end
-
-
##
-
# Converts the URI to a <code>String</code>.
-
#
-
# @return [String] The URI's <code>String</code> representation.
-
1
def to_s
-
79
if self.scheme == nil && self.path != nil && !self.path.empty? &&
-
self.path =~ NORMPATH
-
raise InvalidURIError,
-
"Cannot assemble URI string with ambiguous path: '#{self.path}'"
-
end
-
@uri_string ||= (begin
-
57
uri_string = ""
-
57
uri_string << "#{self.scheme}:" if self.scheme != nil
-
57
uri_string << "//#{self.authority}" if self.authority != nil
-
57
uri_string << self.path.to_s
-
57
uri_string << "?#{self.query}" if self.query != nil
-
57
uri_string << "##{self.fragment}" if self.fragment != nil
-
57
if uri_string.respond_to?(:force_encoding)
-
57
uri_string.force_encoding(Encoding::UTF_8)
-
end
-
57
uri_string
-
79
end)
-
end
-
-
##
-
# URI's are glorified <code>Strings</code>. Allow implicit conversion.
-
1
alias_method :to_str, :to_s
-
-
##
-
# Returns a Hash of the URI components.
-
#
-
# @return [Hash] The URI as a <code>Hash</code> of components.
-
1
def to_hash
-
return {
-
:scheme => self.scheme,
-
:user => self.user,
-
:password => self.password,
-
:host => self.host,
-
:port => self.port,
-
:path => self.path,
-
:query => self.query,
-
:fragment => self.fragment
-
}
-
end
-
-
##
-
# Returns a <code>String</code> representation of the URI object's state.
-
#
-
# @return [String] The URI object's state, as a <code>String</code>.
-
1
def inspect
-
sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s)
-
end
-
-
##
-
# This method allows you to make several changes to a URI simultaneously,
-
# which separately would cause validation errors, but in conjunction,
-
# are valid. The URI will be revalidated as soon as the entire block has
-
# been executed.
-
#
-
# @param [Proc] block
-
# A set of operations to perform on a given URI.
-
1
def defer_validation(&block)
-
87
raise LocalJumpError, "No block given." unless block
-
87
@validation_deferred = true
-
87
block.call()
-
87
@validation_deferred = false
-
87
validate
-
return nil
-
end
-
-
1
private
-
1
SELF_REF = '.'
-
1
PARENT = '..'
-
-
1
RULE_2A = /\/\.\/|\/\.$/
-
1
RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/
-
1
RULE_2D = /^\.\.?\/?/
-
1
RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/
-
-
##
-
# Resolves paths to their simplest form.
-
#
-
# @param [String] path The path to normalize.
-
#
-
# @return [String] The normalized path.
-
1
def self.normalize_path(path)
-
# Section 5.2.4 of RFC 3986
-
-
41
return nil if path.nil?
-
41
normalized_path = path.dup
-
begin
-
41
mod = nil
-
41
mod ||= normalized_path.gsub!(RULE_2A, SLASH)
-
-
41
pair = normalized_path.match(RULE_2B_2C)
-
41
parent, current = pair[1], pair[2] if pair
-
if pair && ((parent != SELF_REF && parent != PARENT) ||
-
41
(current != SELF_REF && current != PARENT))
-
mod ||= normalized_path.gsub!(
-
Regexp.new(
-
"/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
-
"(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
-
), SLASH
-
)
-
end
-
-
41
mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
-
# Non-standard, removes prefixed dotted segments from path.
-
41
mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
-
41
end until mod.nil?
-
-
41
return normalized_path
-
end
-
-
##
-
# Ensures that the URI is valid.
-
1
def validate
-
462
return if !!@validation_deferred
-
91
if self.scheme != nil && self.ip_based? &&
-
(self.host == nil || self.host.empty?) &&
-
(self.path == nil || self.path.empty?)
-
raise InvalidURIError,
-
"Absolute URI missing hierarchical segment: '#{self.to_s}'"
-
end
-
91
if self.host == nil
-
if self.port != nil ||
-
self.user != nil ||
-
self.password != nil
-
raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
-
end
-
end
-
91
if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH &&
-
self.authority != nil
-
raise InvalidURIError,
-
"Cannot have a relative path with an authority set: '#{self.to_s}'"
-
end
-
return nil
-
end
-
-
##
-
# Replaces the internal state of self with the specified URI's state.
-
# Used in destructive operations to avoid massive code repetition.
-
#
-
# @param [Addressable::URI] uri The URI to replace <code>self</code> with.
-
#
-
# @return [Addressable::URI] <code>self</code>.
-
1
def replace_self(uri)
-
# Reset dependant values
-
instance_variables.each do |var|
-
instance_variable_set(var, nil)
-
end
-
-
@scheme = uri.scheme
-
@user = uri.user
-
@password = uri.password
-
@host = uri.host
-
@port = uri.port
-
@path = uri.path
-
@query = uri.query
-
@fragment = uri.fragment
-
return self
-
end
-
-
##
-
# Splits path string with "/"(slash).
-
# It is considered that there is empty string after last slash when
-
# path ends with slash.
-
#
-
# @param [String] path The path to split.
-
#
-
# @return [Array<String>] An array of parts of path.
-
1
def split_path(path)
-
splitted = path.split(SLASH)
-
splitted << EMPTY_STR if path.end_with? SLASH
-
splitted
-
end
-
end
-
end
-
# encoding:utf-8
-
#--
-
# Copyright (C) 2006-2013 Bob Aman
-
#
-
# Licensed under the Apache License, Version 2.0 (the "License");
-
# you may not use this file except in compliance with the License.
-
# You may obtain a copy of the License at
-
#
-
# http://www.apache.org/licenses/LICENSE-2.0
-
#
-
# Unless required by applicable law or agreed to in writing, software
-
# distributed under the License is distributed on an "AS IS" BASIS,
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
# See the License for the specific language governing permissions and
-
# limitations under the License.
-
#++
-
-
-
# Used to prevent the class/module from being loaded more than once
-
1
if !defined?(Addressable::VERSION)
-
1
module Addressable
-
1
module VERSION
-
1
MAJOR = 2
-
1
MINOR = 3
-
1
TINY = 6
-
-
1
STRING = [MAJOR, MINOR, TINY].join('.')
-
end
-
end
-
end
-
1
module Crack
-
1
class ParseError < StandardError; end
-
end
-
-
1
require 'crack/util'
-
1
require 'crack/json'
-
1
require 'crack/xml'
-
# Copyright (c) 2004-2008 David Heinemeier Hansson
-
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
1
require 'safe_yaml/load'
-
1
require 'strscan'
-
-
1
module Crack
-
1
class JSON
-
1
def self.parser_exceptions
-
@parser_exceptions ||= begin
-
exceptions = [ArgumentError]
-
-
if const_defined?(:Psych)
-
if Psych.const_defined?(:SyntaxError)
-
exceptions << Psych::SyntaxError
-
end
-
end
-
-
exceptions
-
end
-
end
-
-
1
def self.parse(json)
-
args = [unescape(convert_json_to_yaml(json))]
-
args << nil if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
-
args << { :whitelisted_tags => ['!ruby/regexp'] }
-
-
SafeYAML.load(*args)
-
-
rescue *parser_exceptions
-
raise ParseError, "Invalid JSON string"
-
end
-
-
1
protected
-
1
def self.unescape(str)
-
# Force the encoding to be UTF-8 so we can perform regular expressions
-
# on 1.9.2 without blowing up.
-
# see http://stackoverflow.com/questions/1224204/ruby-mechanize-getting-force-encoding-exception for a similar issue
-
str.force_encoding('UTF-8') if defined?(Encoding) && str.respond_to?(:force_encoding)
-
str.gsub(/\\u0000/, "").gsub(/\\[u|U]([0-9a-fA-F]{4})/) { [$1.hex].pack("U") }
-
end
-
-
# matches YAML-formatted dates
-
1
DATE_REGEX = /^\d{4}-\d{2}-\d{2}$|^\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)$/
-
-
# Ensure that ":" and "," are always followed by a space
-
1
def self.convert_json_to_yaml(json) #:nodoc:
-
json = String.new(json) #can't modify a frozen string
-
scanner, quoting, marks, pos, date_starts, date_ends = StringScanner.new(json), false, [], nil, [], []
-
while scanner.scan_until(/(\\['"]|['":,\/\\]|\\.)/)
-
case char = scanner[1]
-
when '"', "'"
-
if !quoting
-
quoting = char
-
pos = scanner.pos
-
elsif quoting == char
-
if json[pos..scanner.pos-2] =~ DATE_REGEX
-
# found a date, track the exact positions of the quotes so we can remove them later.
-
# oh, and increment them for each current mark, each one is an extra padded space that bumps
-
# the position in the final YAML output
-
total_marks = marks.size
-
date_starts << pos+total_marks
-
date_ends << scanner.pos+total_marks
-
end
-
quoting = false
-
end
-
when "/"
-
if !quoting
-
json[scanner.pos - 1] = "!ruby/regexp /"
-
scanner.pos += 13
-
scanner.scan_until(/\/[mix]*/)
-
end
-
when ":",","
-
marks << scanner.pos - 1 unless quoting
-
when "\\"
-
scanner.skip(/\\/)
-
end
-
end
-
-
if marks.empty?
-
json.gsub(/\\\//, '/')
-
else
-
left_pos = [-1].push(*marks)
-
right_pos = marks << json.length
-
output = []
-
left_pos.each_with_index do |left, i|
-
output << json[left.succ..right_pos[i]]
-
end
-
output = output * " "
-
-
format_dates(output, date_starts, date_ends)
-
output.gsub!(/\\\//, '/')
-
output
-
end
-
end
-
-
1
def self.format_dates(output, date_starts, date_ends)
-
if YAML.constants.include?('Syck')
-
(date_starts + date_ends).each { |i| output[i-1] = ' ' }
-
else
-
date_starts.each { |i| output[i-2] = '!!timestamp ' }
-
end
-
end
-
end
-
end
-
1
module Crack
-
1
module Util
-
1
def snake_case(str)
-
return str.downcase if str =~ /^[A-Z]+$/
-
str.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/
-
return $+.downcase
-
end
-
-
1
def to_xml_attributes(hash)
-
hash.map do |k,v|
-
%{#{Crack::Util.snake_case(k.to_s).sub(/^(.{1,1})/) { |m| m.downcase }}="#{v.to_s.gsub('"', '"')}"}
-
end.join(' ')
-
end
-
-
1
extend self
-
end
-
end
-
1
require 'rexml/parsers/streamparser'
-
1
require 'rexml/parsers/baseparser'
-
1
require 'rexml/light/node'
-
1
require 'rexml/text'
-
1
require "rexml/document"
-
1
require 'date'
-
1
require 'time'
-
1
require 'yaml'
-
1
require 'bigdecimal'
-
-
# The Reason behind redefining the String Class for this specific plugin is to
-
# avoid the dynamic insertion of stuff on it (see version previous to this commit).
-
# Doing that disables the possibility of efectuating a dump on the structure. This way it goes.
-
1
class REXMLUtiliyNodeString < String
-
1
attr_accessor :attributes
-
end
-
-
# This is a slighly modified version of the XMLUtilityNode from
-
# http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
-
# It's mainly just adding vowels, as I ht cd wth n vwls :)
-
# This represents the hard part of the work, all I did was change the
-
# underlying parser.
-
1
class REXMLUtilityNode #:nodoc:
-
1
attr_accessor :name, :attributes, :children, :type
-
-
1
def self.typecasts
-
11
@@typecasts
-
end
-
-
1
def self.typecasts=(obj)
-
1
@@typecasts = obj
-
end
-
-
1
def self.available_typecasts
-
@@available_typecasts
-
end
-
-
1
def self.available_typecasts=(obj)
-
1
@@available_typecasts = obj
-
end
-
-
1
self.typecasts = {}
-
1
self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i}
-
1
self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")}
-
1
self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
-
1
self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
-
1
self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
-
1
self.typecasts["decimal"] = lambda{|v| v.nil? ? nil : BigDecimal(v.to_s)}
-
1
self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
-
1
self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
-
1
self.typecasts["string"] = lambda{|v| v.to_s}
-
1
self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first }
-
-
1
self.available_typecasts = self.typecasts.keys
-
-
1
def initialize(name, normalized_attributes = {})
-
-
# unnormalize attribute values
-
attributes = Hash[* normalized_attributes.map { |key, value|
-
[ key, unnormalize_xml_entities(value) ]
-
}.flatten]
-
-
@name = name.tr("-", "_")
-
# leave the type alone if we don't know what it is
-
@type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
-
-
@nil_element = attributes.delete("nil") == "true"
-
@attributes = undasherize_keys(attributes)
-
@children = []
-
@text = false
-
end
-
-
1
def add_node(node)
-
@text = true if node.is_a? String
-
@children << node
-
end
-
-
1
def to_hash
-
# ACG: Added a check here to prevent an exception a type == "file" tag has nodes within it
-
if @type == "file" and (@children.first.nil? or @children.first.is_a?(String))
-
f = StringIO.new((@children.first || '').unpack('m').first)
-
class << f
-
attr_accessor :original_filename, :content_type
-
end
-
f.original_filename = attributes['name'] || 'untitled'
-
f.content_type = attributes['content_type'] || 'application/octet-stream'
-
return {name => f}
-
end
-
-
if @text
-
t = typecast_value( unnormalize_xml_entities( inner_html ) )
-
if t.is_a?(String)
-
t = REXMLUtiliyNodeString.new(t)
-
t.attributes = attributes
-
end
-
return { name => t }
-
else
-
#change repeating groups into an array
-
groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
-
-
out = nil
-
if @type == "array"
-
out = []
-
groups.each do |k, v|
-
if v.size == 1
-
out << v.first.to_hash.entries.first.last
-
else
-
out << v.map{|e| e.to_hash[k]}
-
end
-
end
-
out = out.flatten
-
-
else # If Hash
-
out = {}
-
groups.each do |k,v|
-
if v.size == 1
-
out.merge!(v.first)
-
else
-
out.merge!( k => v.map{|e| e.to_hash[k]})
-
end
-
end
-
out.merge! attributes unless attributes.empty?
-
out = out.empty? ? nil : out
-
end
-
-
if @type && out.nil?
-
{ name => typecast_value(out) }
-
else
-
{ name => out }
-
end
-
end
-
end
-
-
# Typecasts a value based upon its type. For instance, if
-
# +node+ has #type == "integer",
-
# {{[node.typecast_value("12") #=> 12]}}
-
#
-
# @param value<String> The value that is being typecast.
-
#
-
# @details [:type options]
-
# "integer"::
-
# converts +value+ to an integer with #to_i
-
# "boolean"::
-
# checks whether +value+, after removing spaces, is the literal
-
# "true"
-
# "datetime"::
-
# Parses +value+ using Time.parse, and returns a UTC Time
-
# "date"::
-
# Parses +value+ using Date.parse
-
#
-
# @return <Integer, TrueClass, FalseClass, Time, Date, Object>
-
# The result of typecasting +value+.
-
#
-
# @note
-
# If +self+ does not have a "type" key, or if it's not one of the
-
# options specified above, the raw +value+ will be returned.
-
1
def typecast_value(value)
-
return value unless @type
-
proc = self.class.typecasts[@type]
-
proc.nil? ? value : proc.call(value)
-
end
-
-
# Take keys of the form foo-bar and convert them to foo_bar
-
1
def undasherize_keys(params)
-
params.keys.each do |key, value|
-
params[key.tr("-", "_")] = params.delete(key)
-
end
-
params
-
end
-
-
# Get the inner_html of the REXML node.
-
1
def inner_html
-
@children.join
-
end
-
-
# Converts the node into a readable HTML node.
-
#
-
# @return <String> The HTML node in text form.
-
1
def to_html
-
attributes.merge!(:type => @type ) if @type
-
"<#{name}#{Crack::Util.to_xml_attributes(attributes)}>#{@nil_element ? '' : inner_html}</#{name}>"
-
end
-
-
# @alias #to_html #to_s
-
1
def to_s
-
to_html
-
end
-
-
1
private
-
-
1
def unnormalize_xml_entities value
-
REXML::Text.unnormalize(value)
-
end
-
end
-
-
1
module Crack
-
1
class REXMLParser
-
1
def self.parse(xml)
-
stack = []
-
parser = REXML::Parsers::BaseParser.new(xml)
-
-
while true
-
event = parser.pull
-
case event[0]
-
when :end_document
-
break
-
when :end_doctype, :start_doctype
-
# do nothing
-
when :start_element
-
stack.push REXMLUtilityNode.new(event[1], event[2])
-
when :end_element
-
if stack.size > 1
-
temp = stack.pop
-
stack.last.add_node(temp)
-
end
-
when :text, :cdata
-
stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
-
end
-
end
-
-
stack.length > 0 ? stack.pop.to_hash : {}
-
end
-
end
-
-
1
class XML
-
1
def self.parser
-
@@parser ||= REXMLParser
-
end
-
-
1
def self.parser=(parser)
-
@@parser = parser
-
end
-
-
1
def self.parse(xml)
-
parser.parse(xml)
-
end
-
end
-
end
-
# -*- ruby encoding: utf-8 -*-
-
-
1
module Diff; end unless defined? Diff
-
# = Diff::LCS 1.2.5
-
#
-
# Computes "intelligent" differences between two sequenced Enumerables. This
-
# is an implementation of the McIlroy-Hunt "diff" algorithm for Enumerable
-
# objects that include Diffable.
-
#
-
# Based on Mario I. Wolczko's Smalltalk version (1.2, 1993) and Ned Konz's
-
# Perl version (Algorithm::Diff 1.15).
-
#
-
# == Synopsis
-
# require 'diff/lcs'
-
#
-
# seq1 = %w(a b c e h j l m n p)
-
# seq2 = %w(b c d e f j k l m r s t)
-
#
-
# lcs = Diff::LCS.lcs(seq1, seq2)
-
# diffs = Diff::LCS.diff(seq1, seq2)
-
# sdiff = Diff::LCS.sdiff(seq1, seq2)
-
# seq = Diff::LCS.traverse_sequences(seq1, seq2, callback_obj)
-
# bal = Diff::LCS.traverse_balanced(seq1, seq2, callback_obj)
-
# seq2 == Diff::LCS.patch(seq1, diffs)
-
# seq2 == Diff::LCS.patch!(seq1, diffs)
-
# seq1 == Diff::LCS.unpatch(seq2, diffs)
-
# seq1 == Diff::LCS.unpatch!(seq2, diffs)
-
# seq2 == Diff::LCS.patch(seq1, sdiff)
-
# seq2 == Diff::LCS.patch!(seq1, sdiff)
-
# seq1 == Diff::LCS.unpatch(seq2, sdiff)
-
# seq1 == Diff::LCS.unpatch!(seq2, sdiff)
-
#
-
# Alternatively, objects can be extended with Diff::LCS:
-
#
-
# seq1.extend(Diff::LCS)
-
# lcs = seq1.lcs(seq2)
-
# diffs = seq1.diff(seq2)
-
# sdiff = seq1.sdiff(seq2)
-
# seq = seq1.traverse_sequences(seq2, callback_obj)
-
# bal = seq1.traverse_balanced(seq2, callback_obj)
-
# seq2 == seq1.patch(diffs)
-
# seq2 == seq1.patch!(diffs)
-
# seq1 == seq2.unpatch(diffs)
-
# seq1 == seq2.unpatch!(diffs)
-
# seq2 == seq1.patch(sdiff)
-
# seq2 == seq1.patch!(sdiff)
-
# seq1 == seq2.unpatch(sdiff)
-
# seq1 == seq2.unpatch!(sdiff)
-
#
-
# Default extensions are provided for Array and String objects through the
-
# use of 'diff/lcs/array' and 'diff/lcs/string'.
-
#
-
# == Introduction (by Mark-Jason Dominus)
-
#
-
# <em>The following text is from the Perl documentation. The only changes
-
# have been to make the text appear better in Rdoc</em>.
-
#
-
# I once read an article written by the authors of +diff+; they said that
-
# they hard worked very hard on the algorithm until they found the right
-
# one.
-
#
-
# I think what they ended up using (and I hope someone will correct me,
-
# because I am not very confident about this) was the `longest common
-
# subsequence' method. In the LCS problem, you have two sequences of items:
-
#
-
# a b c d f g h j q z
-
# a b c d e f g i j k r x y z
-
#
-
# and you want to find the longest sequence of items that is present in both
-
# original sequences in the same order. That is, you want to find a new
-
# sequence *S* which can be obtained from the first sequence by deleting
-
# some items, and from the second sequence by deleting other items. You also
-
# want *S* to be as long as possible. In this case *S* is:
-
#
-
# a b c d f g j z
-
#
-
# From there it's only a small step to get diff-like output:
-
#
-
# e h i k q r x y
-
# + - + + - + + +
-
#
-
# This module solves the LCS problem. It also includes a canned function to
-
# generate +diff+-like output.
-
#
-
# It might seem from the example above that the LCS of two sequences is
-
# always pretty obvious, but that's not always the case, especially when the
-
# two sequences have many repeated elements. For example, consider
-
#
-
# a x b y c z p d q
-
# a b c a x b y c z
-
#
-
# A naive approach might start by matching up the +a+ and +b+ that appear at
-
# the beginning of each sequence, like this:
-
#
-
# a x b y c z p d q
-
# a b c a b y c z
-
#
-
# This finds the common subsequence +a b c z+. But actually, the LCS is +a x
-
# b y c z+:
-
#
-
# a x b y c z p d q
-
# a b c a x b y c z
-
#
-
# == Author
-
# This version is by Austin Ziegler <austin@rubyforge.org>.
-
#
-
# It is based on the Perl Algorithm::Diff (1.15) by Ned Konz , copyright
-
# © 2000–2002 and the Smalltalk diff version by Mario I.
-
# Wolczko, copyright © 1993. Documentation includes work by
-
# Mark-Jason Dominus.
-
#
-
# == Licence
-
# Copyright © 2004–2013 Austin Ziegler
-
# This program is free software; you can redistribute it and/or modify it
-
# under the same terms as Ruby, or alternatively under the Perl Artistic
-
# licence.
-
#
-
# == Credits
-
# Much of the documentation is taken directly from the Perl Algorithm::Diff
-
# implementation and was written originally by Mark-Jason Dominus and later
-
# by Ned Konz. The basic Ruby implementation was re-ported from the
-
# Smalltalk implementation, available at
-
# ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st
-
#
-
# #sdiff and #traverse_balanced were written for the Perl version by Mike
-
# Schilli <m@perlmeister.com>.
-
#
-
# "The algorithm is described in <em>A Fast Algorithm for Computing Longest
-
# Common Subsequences</em>, CACM, vol.20, no.5, pp.350-353, May
-
# 1977, with a few minor improvements to improve the speed."
-
1
module Diff::LCS
-
1
VERSION = '1.2.5'
-
end
-
-
1
require 'diff/lcs/callbacks'
-
1
require 'diff/lcs/internals'
-
-
1
module Diff::LCS
-
# Returns an Array containing the longest common subsequence(s) between
-
# +self+ and +other+. See Diff::LCS#LCS.
-
#
-
# lcs = seq1.lcs(seq2)
-
1
def lcs(other, &block) #:yields self[i] if there are matched subsequences:
-
Diff::LCS.lcs(self, other, &block)
-
end
-
-
# Returns the difference set between +self+ and +other+. See
-
# Diff::LCS#diff.
-
1
def diff(other, callbacks = nil, &block)
-
Diff::LCS.diff(self, other, callbacks, &block)
-
end
-
-
# Returns the balanced ("side-by-side") difference set between +self+ and
-
# +other+. See Diff::LCS#sdiff.
-
1
def sdiff(other, callbacks = nil, &block)
-
Diff::LCS.sdiff(self, other, callbacks, &block)
-
end
-
-
# Traverses the discovered longest common subsequences between +self+ and
-
# +other+. See Diff::LCS#traverse_sequences.
-
1
def traverse_sequences(other, callbacks = nil, &block)
-
traverse_sequences(self, other, callbacks ||
-
Diff::LCS.YieldingCallbacks, &block)
-
end
-
-
# Traverses the discovered longest common subsequences between +self+ and
-
# +other+ using the alternate, balanced algorithm. See
-
# Diff::LCS#traverse_balanced.
-
1
def traverse_balanced(other, callbacks = nil, &block)
-
traverse_balanced(self, other, callbacks ||
-
Diff::LCS.YieldingCallbacks, &block)
-
end
-
-
# Attempts to patch +self+ with the provided +patchset+. A new sequence
-
# based on +self+ and the +patchset+ will be created. See Diff::LCS#patch.
-
# Attempts to autodiscover the direction of the patch.
-
1
def patch(patchset)
-
Diff::LCS.patch(self, patchset)
-
end
-
1
alias_method :unpatch, :patch
-
-
# Attempts to patch +self+ with the provided +patchset+. A new sequence
-
# based on +self+ and the +patchset+ will be created. See Diff::LCS#patch.
-
# Does no patch direction autodiscovery.
-
1
def patch!(patchset)
-
Diff::LCS.patch!(self, patchset)
-
end
-
-
# Attempts to unpatch +self+ with the provided +patchset+. A new sequence
-
# based on +self+ and the +patchset+ will be created. See Diff::LCS#unpatch.
-
# Does no patch direction autodiscovery.
-
1
def unpatch!(patchset)
-
Diff::LCS.unpatch!(self, patchset)
-
end
-
-
# Attempts to patch +self+ with the provided +patchset+, using #patch!. If
-
# the sequence this is used on supports #replace, the value of +self+ will
-
# be replaced. See Diff::LCS#patch. Does no patch direction autodiscovery.
-
1
def patch_me(patchset)
-
if respond_to? :replace
-
replace(patch!(patchset))
-
else
-
patch!(patchset)
-
end
-
end
-
-
# Attempts to unpatch +self+ with the provided +patchset+, using
-
# #unpatch!. If the sequence this is used on supports #replace, the value
-
# of +self+ will be replaced. See Diff::LCS#unpatch. Does no patch direction
-
# autodiscovery.
-
1
def unpatch_me(patchset)
-
if respond_to? :replace
-
replace(unpatch!(patchset))
-
else
-
unpatch!(patchset)
-
end
-
end
-
end
-
-
1
class << Diff::LCS
-
1
def lcs(seq1, seq2, &block) #:yields seq1[i] for each matched:
-
matches = Diff::LCS::Internals.lcs(seq1, seq2)
-
ret = []
-
string = seq1.kind_of? String
-
matches.each_with_index do |e, i|
-
unless matches[i].nil?
-
v = string ? seq1[i, 1] : seq1[i]
-
v = block[v] if block
-
ret << v
-
end
-
end
-
ret
-
end
-
1
alias_method :LCS, :lcs
-
-
# #diff computes the smallest set of additions and deletions necessary to
-
# turn the first sequence into the second, and returns a description of
-
# these changes.
-
#
-
# See Diff::LCS::DiffCallbacks for the default behaviour. An alternate
-
# behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a
-
# Class argument is provided for +callbacks+, #diff will attempt to
-
# initialise it. If the +callbacks+ object (possibly initialised) responds
-
# to #finish, it will be called.
-
1
def diff(seq1, seq2, callbacks = nil, &block) # :yields diff changes:
-
diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks,
-
&block)
-
end
-
-
# #sdiff computes all necessary components to show two sequences and their
-
# minimized differences side by side, just like the Unix utility
-
# <em>sdiff</em> does:
-
#
-
# old < -
-
# same same
-
# before | after
-
# - > new
-
#
-
# See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate
-
# behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a
-
# Class argument is provided for +callbacks+, #diff will attempt to
-
# initialise it. If the +callbacks+ object (possibly initialised) responds
-
# to #finish, it will be called.
-
1
def sdiff(seq1, seq2, callbacks = nil, &block) #:yields diff changes:
-
diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks,
-
&block)
-
end
-
-
# #traverse_sequences is the most general facility provided by this
-
# module; #diff and #lcs are implemented as calls to it.
-
#
-
# The arguments to #traverse_sequences are the two sequences to traverse,
-
# and a callback object, like this:
-
#
-
# traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
-
#
-
# == Callback Methods
-
#
-
# Optional callback methods are <em>emphasized</em>.
-
#
-
# callbacks#match:: Called when +a+ and +b+ are pointing to
-
# common elements in +A+ and +B+.
-
# callbacks#discard_a:: Called when +a+ is pointing to an
-
# element not in +B+.
-
# callbacks#discard_b:: Called when +b+ is pointing to an
-
# element not in +A+.
-
# <em>callbacks#finished_a</em>:: Called when +a+ has reached the end of
-
# sequence +A+.
-
# <em>callbacks#finished_b</em>:: Called when +b+ has reached the end of
-
# sequence +B+.
-
#
-
# == Algorithm
-
#
-
# a---+
-
# v
-
# A = a b c e h j l m n p
-
# B = b c d e f j k l m r s t
-
# ^
-
# b---+
-
#
-
# If there are two arrows (+a+ and +b+) pointing to elements of sequences
-
# +A+ and +B+, the arrows will initially point to the first elements of
-
# their respective sequences. #traverse_sequences will advance the arrows
-
# through the sequences one element at a time, calling a method on the
-
# user-specified callback object before each advance. It will advance the
-
# arrows in such a way that if there are elements <tt>A[i]</tt> and
-
# <tt>B[j]</tt> which are both equal and part of the longest common
-
# subsequence, there will be some moment during the execution of
-
# #traverse_sequences when arrow +a+ is pointing to <tt>A[i]</tt> and
-
# arrow +b+ is pointing to <tt>B[j]</tt>. When this happens,
-
# #traverse_sequences will call <tt>callbacks#match</tt> and then it will
-
# advance both arrows.
-
#
-
# Otherwise, one of the arrows is pointing to an element of its sequence
-
# that is not part of the longest common subsequence. #traverse_sequences
-
# will advance that arrow and will call <tt>callbacks#discard_a</tt> or
-
# <tt>callbacks#discard_b</tt>, depending on which arrow it advanced. If
-
# both arrows point to elements that are not part of the longest common
-
# subsequence, then #traverse_sequences will advance one of them and call
-
# the appropriate callback, but it is not specified which it will call.
-
#
-
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
-
# and <tt>callbacks#discard_b</tt> are invoked with an event comprising
-
# the action ("=", "+", or "-", respectively), the indicies +i+ and +j+,
-
# and the elements <tt>A[i]</tt> and <tt>B[j]</tt>. Return values are
-
# discarded by #traverse_sequences.
-
#
-
# === End of Sequences
-
#
-
# If arrow +a+ reaches the end of its sequence before arrow +b+ does,
-
# #traverse_sequence will try to call <tt>callbacks#finished_a</tt> with
-
# the last index and element of +A+ (<tt>A[-1]</tt>) and the current index
-
# and element of +B+ (<tt>B[j]</tt>). If <tt>callbacks#finished_a</tt>
-
# does not exist, then <tt>callbacks#discard_b</tt> will be called on each
-
# element of +B+ until the end of the sequence is reached (the call will
-
# be done with <tt>A[-1]</tt> and <tt>B[j]</tt> for each element).
-
#
-
# If +b+ reaches the end of +B+ before +a+ reaches the end of +A+,
-
# <tt>callbacks#finished_b</tt> will be called with the current index and
-
# element of +A+ (<tt>A[i]</tt>) and the last index and element of +B+
-
# (<tt>A[-1]</tt>). Again, if <tt>callbacks#finished_b</tt> does not exist
-
# on the callback object, then <tt>callbacks#discard_a</tt> will be called
-
# on each element of +A+ until the end of the sequence is reached
-
# (<tt>A[i]</tt> and <tt>B[-1]</tt>).
-
#
-
# There is a chance that one additional <tt>callbacks#discard_a</tt> or
-
# <tt>callbacks#discard_b</tt> will be called after the end of the
-
# sequence is reached, if +a+ has not yet reached the end of +A+ or +b+
-
# has not yet reached the end of +B+.
-
1
def traverse_sequences(seq1, seq2, callbacks = Diff::LCS::SequenceCallbacks, &block) #:yields change events:
-
callbacks ||= Diff::LCS::SequenceCallbacks
-
matches = Diff::LCS::Internals.lcs(seq1, seq2)
-
-
run_finished_a = run_finished_b = false
-
string = seq1.kind_of?(String)
-
-
a_size = seq1.size
-
b_size = seq2.size
-
ai = bj = 0
-
-
(0..matches.size).each do |i|
-
b_line = matches[i]
-
-
ax = string ? seq1[i, 1] : seq1[i]
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
-
if b_line.nil?
-
unless ax.nil? or (string and ax.empty?)
-
event = Diff::LCS::ContextChange.new('-', i, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_a(event)
-
end
-
else
-
loop do
-
break unless bj < b_line
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
event = Diff::LCS::ContextChange.new('+', i, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_b(event)
-
bj += 1
-
end
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
event = Diff::LCS::ContextChange.new('=', i, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.match(event)
-
bj += 1
-
end
-
ai = i
-
end
-
ai += 1
-
-
# The last entry (if any) processed was a match. +ai+ and +bj+ point
-
# just past the last matching lines in their sequences.
-
while (ai < a_size) or (bj < b_size)
-
# last A?
-
if ai == a_size and bj < b_size
-
if callbacks.respond_to?(:finished_a) and not run_finished_a
-
ax = string ? seq1[-1, 1] : seq1[-1]
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
event = Diff::LCS::ContextChange.new('>', (a_size - 1), ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.finished_a(event)
-
run_finished_a = true
-
else
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
loop do
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_b(event)
-
bj += 1
-
break unless bj < b_size
-
end
-
end
-
end
-
-
# last B?
-
if bj == b_size and ai < a_size
-
if callbacks.respond_to?(:finished_b) and not run_finished_b
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
bx = string ? seq2[-1, 1] : seq2[-1]
-
event = Diff::LCS::ContextChange.new('<', ai, ax, (b_size - 1), bx)
-
event = yield event if block_given?
-
callbacks.finished_b(event)
-
run_finished_b = true
-
else
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
loop do
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_a(event)
-
ai += 1
-
break unless bj < b_size
-
end
-
end
-
end
-
-
if ai < a_size
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_a(event)
-
ai += 1
-
end
-
-
if bj < b_size
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_b(event)
-
bj += 1
-
end
-
end
-
end
-
-
# #traverse_balanced is an alternative to #traverse_sequences. It uses a
-
# different algorithm to iterate through the entries in the computed
-
# longest common subsequence. Instead of viewing the changes as insertions
-
# or deletions from one of the sequences, #traverse_balanced will report
-
# <em>changes</em> between the sequences.
-
#
-
# The arguments to #traverse_balanced are the two sequences to traverse
-
# and a callback object, like this:
-
#
-
# traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
-
#
-
# #sdiff is implemented with #traverse_balanced.
-
#
-
# == Callback Methods
-
#
-
# Optional callback methods are <em>emphasized</em>.
-
#
-
# callbacks#match:: Called when +a+ and +b+ are pointing to
-
# common elements in +A+ and +B+.
-
# callbacks#discard_a:: Called when +a+ is pointing to an
-
# element not in +B+.
-
# callbacks#discard_b:: Called when +b+ is pointing to an
-
# element not in +A+.
-
# <em>callbacks#change</em>:: Called when +a+ and +b+ are pointing to
-
# the same relative position, but
-
# <tt>A[a]</tt> and <tt>B[b]</tt> are not
-
# the same; a <em>change</em> has
-
# occurred.
-
#
-
# #traverse_balanced might be a bit slower than #traverse_sequences,
-
# noticable only while processing huge amounts of data.
-
#
-
# == Algorithm
-
#
-
# a---+
-
# v
-
# A = a b c e h j l m n p
-
# B = b c d e f j k l m r s t
-
# ^
-
# b---+
-
#
-
# === Matches
-
#
-
# If there are two arrows (+a+ and +b+) pointing to elements of sequences
-
# +A+ and +B+, the arrows will initially point to the first elements of
-
# their respective sequences. #traverse_sequences will advance the arrows
-
# through the sequences one element at a time, calling a method on the
-
# user-specified callback object before each advance. It will advance the
-
# arrows in such a way that if there are elements <tt>A[i]</tt> and
-
# <tt>B[j]</tt> which are both equal and part of the longest common
-
# subsequence, there will be some moment during the execution of
-
# #traverse_sequences when arrow +a+ is pointing to <tt>A[i]</tt> and
-
# arrow +b+ is pointing to <tt>B[j]</tt>. When this happens,
-
# #traverse_sequences will call <tt>callbacks#match</tt> and then it will
-
# advance both arrows.
-
#
-
# === Discards
-
#
-
# Otherwise, one of the arrows is pointing to an element of its sequence
-
# that is not part of the longest common subsequence. #traverse_sequences
-
# will advance that arrow and will call <tt>callbacks#discard_a</tt> or
-
# <tt>callbacks#discard_b</tt>, depending on which arrow it advanced.
-
#
-
# === Changes
-
#
-
# If both +a+ and +b+ point to elements that are not part of the longest
-
# common subsequence, then #traverse_sequences will try to call
-
# <tt>callbacks#change</tt> and advance both arrows. If
-
# <tt>callbacks#change</tt> is not implemented, then
-
# <tt>callbacks#discard_a</tt> and <tt>callbacks#discard_b</tt> will be
-
# called in turn.
-
#
-
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
-
# <tt>callbacks#discard_b</tt>, and <tt>callbacks#change</tt> are invoked
-
# with an event comprising the action ("=", "+", "-", or "!",
-
# respectively), the indicies +i+ and +j+, and the elements
-
# <tt>A[i]</tt> and <tt>B[j]</tt>. Return values are discarded by
-
# #traverse_balanced.
-
#
-
# === Context
-
# Note that +i+ and +j+ may not be the same index position, even if +a+
-
# and +b+ are considered to be pointing to matching or changed elements.
-
1
def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
-
matches = Diff::LCS::Internals.lcs(seq1, seq2)
-
a_size = seq1.size
-
b_size = seq2.size
-
ai = bj = mb = 0
-
ma = -1
-
string = seq1.kind_of?(String)
-
-
# Process all the lines in the match vector.
-
loop do
-
# Find next match indices +ma+ and +mb+
-
loop do
-
ma += 1
-
break unless ma < matches.size and matches[ma].nil?
-
end
-
-
break if ma >= matches.size # end of matches?
-
mb = matches[ma]
-
-
# Change(seq2)
-
while (ai < ma) or (bj < mb)
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
-
case [(ai < ma), (bj < mb)]
-
when [true, true]
-
if callbacks.respond_to?(:change)
-
event = Diff::LCS::ContextChange.new('!', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.change(event)
-
ai += 1
-
bj += 1
-
else
-
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_a(event)
-
ai += 1
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_b(event)
-
bj += 1
-
end
-
when [true, false]
-
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_a(event)
-
ai += 1
-
when [false, true]
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_b(event)
-
bj += 1
-
end
-
end
-
-
# Match
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
event = Diff::LCS::ContextChange.new('=', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.match(event)
-
ai += 1
-
bj += 1
-
end
-
-
while (ai < a_size) or (bj < b_size)
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
bx = string ? seq2[bj, 1] : seq2[bj]
-
-
case [(ai < a_size), (bj < b_size)]
-
when [true, true]
-
if callbacks.respond_to?(:change)
-
event = Diff::LCS::ContextChange.new('!', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.change(event)
-
ai += 1
-
bj += 1
-
else
-
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_a(event)
-
ai += 1
-
ax = string ? seq1[ai, 1] : seq1[ai]
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_b(event)
-
bj += 1
-
end
-
when [true, false]
-
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_a(event)
-
ai += 1
-
when [false, true]
-
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
-
event = yield event if block_given?
-
callbacks.discard_b(event)
-
bj += 1
-
end
-
end
-
end
-
-
1
PATCH_MAP = { #:nodoc:
-
:patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' },
-
:unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' }
-
}
-
-
# Applies a +patchset+ to the sequence +src+ according to the +direction+
-
# (<tt>:patch</tt> or <tt>:unpatch</tt>), producing a new sequence.
-
#
-
# If the +direction+ is not specified, Diff::LCS::patch will attempt to
-
# discover the direction of the +patchset+.
-
#
-
# A +patchset+ can be considered to apply forward (<tt>:patch</tt>) if the
-
# following expression is true:
-
#
-
# patch(s1, diff(s1, s2)) -> s2
-
#
-
# A +patchset+ can be considered to apply backward (<tt>:unpatch</tt>) if
-
# the following expression is true:
-
#
-
# patch(s2, diff(s1, s2)) -> s1
-
#
-
# If the +patchset+ contains no changes, the +src+ value will be returned
-
# as either <tt>src.dup</tt> or +src+. A +patchset+ can be deemed as
-
# having no changes if the following predicate returns true:
-
#
-
# patchset.empty? or
-
# patchset.flatten.all? { |change| change.unchanged? }
-
#
-
# === Patchsets
-
#
-
# A +patchset+ is always an enumerable sequence of changes, hunks of
-
# changes, or a mix of the two. A hunk of changes is an enumerable
-
# sequence of changes:
-
#
-
# [ # patchset
-
# # change
-
# [ # hunk
-
# # change
-
# ]
-
# ]
-
#
-
# The +patch+ method accepts <tt>patchset</tt>s that are enumerable
-
# sequences containing either Diff::LCS::Change objects (or a subclass) or
-
# the array representations of those objects. Prior to application, array
-
# representations of Diff::LCS::Change objects will be reified.
-
1
def patch(src, patchset, direction = nil)
-
# Normalize the patchset.
-
has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset)
-
-
if not has_changes
-
return src.dup if src.respond_to? :dup
-
return src
-
end
-
-
string = src.kind_of?(String)
-
# Start with a new empty type of the source's class
-
res = src.class.new
-
-
direction ||= Diff::LCS::Internals.intuit_diff_direction(src, patchset)
-
-
ai = bj = 0
-
-
patch_map = PATCH_MAP[direction]
-
-
patchset.flatten.each do |change|
-
# Both Change and ContextChange support #action
-
action = patch_map[change.action]
-
-
case change
-
when Diff::LCS::ContextChange
-
case direction
-
when :patch
-
el = change.new_element
-
op = change.old_position
-
np = change.new_position
-
when :unpatch
-
el = change.old_element
-
op = change.new_position
-
np = change.old_position
-
end
-
-
case action
-
when '-' # Remove details from the old string
-
while ai < op
-
res << (string ? src[ai, 1] : src[ai])
-
ai += 1
-
bj += 1
-
end
-
ai += 1
-
when '+'
-
while bj < np
-
res << (string ? src[ai, 1] : src[ai])
-
ai += 1
-
bj += 1
-
end
-
-
res << el
-
bj += 1
-
when '='
-
# This only appears in sdiff output with the SDiff callback.
-
# Therefore, we only need to worry about dealing with a single
-
# element.
-
res << el
-
-
ai += 1
-
bj += 1
-
when '!'
-
while ai < op
-
res << (string ? src[ai, 1] : src[ai])
-
ai += 1
-
bj += 1
-
end
-
-
bj += 1
-
ai += 1
-
-
res << el
-
end
-
when Diff::LCS::Change
-
case action
-
when '-'
-
while ai < change.position
-
res << (string ? src[ai, 1] : src[ai])
-
ai += 1
-
bj += 1
-
end
-
ai += 1
-
when '+'
-
while bj < change.position
-
res << (string ? src[ai, 1] : src[ai])
-
ai += 1
-
bj += 1
-
end
-
-
bj += 1
-
-
res << change.element
-
end
-
end
-
end
-
-
while ai < src.size
-
res << (string ? src[ai, 1] : src[ai])
-
ai += 1
-
bj += 1
-
end
-
-
res
-
end
-
-
# Given a set of patchset, convert the current version to the prior
-
# version. Does no auto-discovery.
-
1
def unpatch!(src, patchset)
-
patch(src, patchset, :unpatch)
-
end
-
-
# Given a set of patchset, convert the current version to the next
-
# version. Does no auto-discovery.
-
1
def patch!(src, patchset)
-
patch(src, patchset, :patch)
-
end
-
end
-
# -*- ruby encoding: utf-8 -*-
-
-
# A block is an operation removing, adding, or changing a group of items.
-
# Basically, this is just a list of changes, where each change adds or
-
# deletes a single item. Used by bin/ldiff.
-
1
class Diff::LCS::Block
-
1
attr_reader :changes, :insert, :remove
-
-
1
def initialize(chunk)
-
@changes = []
-
@insert = []
-
@remove = []
-
-
chunk.each do |item|
-
@changes << item
-
@remove << item if item.deleting?
-
@insert << item if item.adding?
-
end
-
end
-
-
1
def diff_size
-
@insert.size - @remove.size
-
end
-
-
1
def op
-
case [@remove.empty?, @insert.empty?]
-
when [false, false]
-
'!'
-
when [false, true]
-
'-'
-
when [true, false]
-
'+'
-
else # [true, true]
-
'^'
-
end
-
end
-
end
-
# -*- ruby encoding: utf-8 -*-
-
-
1
require 'diff/lcs/change'
-
-
1
module Diff::LCS
-
# This callback object implements the default set of callback events,
-
# which only returns the event itself. Note that #finished_a and
-
# #finished_b are not implemented -- I haven't yet figured out where they
-
# would be useful.
-
#
-
# Note that this is intended to be called as is, e.g.,
-
#
-
# Diff::LCS.LCS(seq1, seq2, Diff::LCS::DefaultCallbacks)
-
1
class DefaultCallbacks
-
1
class << self
-
# Called when two items match.
-
1
def match(event)
-
event
-
end
-
# Called when the old value is discarded in favour of the new value.
-
1
def discard_a(event)
-
event
-
end
-
# Called when the new value is discarded in favour of the old value.
-
1
def discard_b(event)
-
event
-
end
-
# Called when both the old and new values have changed.
-
1
def change(event)
-
event
-
end
-
-
1
private :new
-
end
-
end
-
-
# An alias for DefaultCallbacks that is used in
-
# Diff::LCS#traverse_sequences.
-
#
-
# Diff::LCS.LCS(seq1, seq2, Diff::LCS::SequenceCallbacks)
-
1
SequenceCallbacks = DefaultCallbacks
-
-
# An alias for DefaultCallbacks that is used in
-
# Diff::LCS#traverse_balanced.
-
#
-
# Diff::LCS.LCS(seq1, seq2, Diff::LCS::BalancedCallbacks)
-
1
BalancedCallbacks = DefaultCallbacks
-
-
1
def self.callbacks_for(callbacks)
-
callbacks.new rescue callbacks
-
end
-
end
-
-
# This will produce a compound array of simple diff change objects. Each
-
# element in the #diffs array is a +hunk+ or +hunk+ array, where each
-
# element in each +hunk+ array is a single Change object representing the
-
# addition or removal of a single element from one of the two tested
-
# sequences. The +hunk+ provides the full context for the changes.
-
#
-
# diffs = Diff::LCS.diff(seq1, seq2)
-
# # This example shows a simplified array format.
-
# # [ [ [ '-', 0, 'a' ] ], # 1
-
# # [ [ '+', 2, 'd' ] ], # 2
-
# # [ [ '-', 4, 'h' ], # 3
-
# # [ '+', 4, 'f' ] ],
-
# # [ [ '+', 6, 'k' ] ], # 4
-
# # [ [ '-', 8, 'n' ], # 5
-
# # [ '-', 9, 'p' ],
-
# # [ '+', 9, 'r' ],
-
# # [ '+', 10, 's' ],
-
# # [ '+', 11, 't' ] ] ]
-
#
-
# There are five hunks here. The first hunk says that the +a+ at position 0
-
# of the first sequence should be deleted (<tt>'-'</tt>). The second hunk
-
# says that the +d+ at position 2 of the second sequence should be inserted
-
# (<tt>'+'</tt>). The third hunk says that the +h+ at position 4 of the
-
# first sequence should be removed and replaced with the +f+ from position 4
-
# of the second sequence. The other two hunks are described similarly.
-
#
-
# === Use
-
#
-
# This callback object must be initialised and is used by the Diff::LCS#diff
-
# method.
-
#
-
# cbo = Diff::LCS::DiffCallbacks.new
-
# Diff::LCS.LCS(seq1, seq2, cbo)
-
# cbo.finish
-
#
-
# Note that the call to #finish is absolutely necessary, or the last set of
-
# changes will not be visible. Alternatively, can be used as:
-
#
-
# cbo = Diff::LCS::DiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
-
#
-
# The necessary #finish call will be made.
-
#
-
# === Simplified Array Format
-
#
-
# The simplified array format used in the example above can be obtained
-
# with:
-
#
-
# require 'pp'
-
# pp diffs.map { |e| e.map { |f| f.to_a } }
-
1
class Diff::LCS::DiffCallbacks
-
# Returns the difference set collected during the diff process.
-
1
attr_reader :diffs
-
-
1
def initialize # :yields self:
-
@hunk = []
-
@diffs = []
-
-
if block_given?
-
begin
-
yield self
-
ensure
-
self.finish
-
end
-
end
-
end
-
-
# Finalizes the diff process. If an unprocessed hunk still exists, then it
-
# is appended to the diff list.
-
1
def finish
-
finish_hunk
-
end
-
-
1
def match(event)
-
finish_hunk
-
end
-
-
1
def discard_a(event)
-
@hunk << Diff::LCS::Change.new('-', event.old_position, event.old_element)
-
end
-
-
1
def discard_b(event)
-
@hunk << Diff::LCS::Change.new('+', event.new_position, event.new_element)
-
end
-
-
1
def finish_hunk
-
@diffs << @hunk unless @hunk.empty?
-
@hunk = []
-
end
-
1
private :finish_hunk
-
end
-
-
# This will produce a compound array of contextual diff change objects. Each
-
# element in the #diffs array is a "hunk" array, where each element in each
-
# "hunk" array is a single change. Each change is a Diff::LCS::ContextChange
-
# that contains both the old index and new index values for the change. The
-
# "hunk" provides the full context for the changes. Both old and new objects
-
# will be presented for changed objects. +nil+ will be substituted for a
-
# discarded object.
-
#
-
# seq1 = %w(a b c e h j l m n p)
-
# seq2 = %w(b c d e f j k l m r s t)
-
#
-
# diffs = Diff::LCS.diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks)
-
# # This example shows a simplified array format.
-
# # [ [ [ '-', [ 0, 'a' ], [ 0, nil ] ] ], # 1
-
# # [ [ '+', [ 3, nil ], [ 2, 'd' ] ] ], # 2
-
# # [ [ '-', [ 4, 'h' ], [ 4, nil ] ], # 3
-
# # [ '+', [ 5, nil ], [ 4, 'f' ] ] ],
-
# # [ [ '+', [ 6, nil ], [ 6, 'k' ] ] ], # 4
-
# # [ [ '-', [ 8, 'n' ], [ 9, nil ] ], # 5
-
# # [ '+', [ 9, nil ], [ 9, 'r' ] ],
-
# # [ '-', [ 9, 'p' ], [ 10, nil ] ],
-
# # [ '+', [ 10, nil ], [ 10, 's' ] ],
-
# # [ '+', [ 10, nil ], [ 11, 't' ] ] ] ]
-
#
-
# The five hunks shown are comprised of individual changes; if there is a
-
# related set of changes, they are still shown individually.
-
#
-
# This callback can also be used with Diff::LCS#sdiff, which will produce
-
# results like:
-
#
-
# diffs = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextCallbacks)
-
# # This example shows a simplified array format.
-
# # [ [ [ "-", [ 0, "a" ], [ 0, nil ] ] ], # 1
-
# # [ [ "+", [ 3, nil ], [ 2, "d" ] ] ], # 2
-
# # [ [ "!", [ 4, "h" ], [ 4, "f" ] ] ], # 3
-
# # [ [ "+", [ 6, nil ], [ 6, "k" ] ] ], # 4
-
# # [ [ "!", [ 8, "n" ], [ 9, "r" ] ], # 5
-
# # [ "!", [ 9, "p" ], [ 10, "s" ] ],
-
# # [ "+", [ 10, nil ], [ 11, "t" ] ] ] ]
-
#
-
# The five hunks are still present, but are significantly shorter in total
-
# presentation, because changed items are shown as changes ("!") instead of
-
# potentially "mismatched" pairs of additions and deletions.
-
#
-
# The result of this operation is similar to that of
-
# Diff::LCS::SDiffCallbacks. They may be compared as:
-
#
-
# s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
-
# c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten
-
#
-
# s == c # -> true
-
#
-
# === Use
-
#
-
# This callback object must be initialised and can be used by the
-
# Diff::LCS#diff or Diff::LCS#sdiff methods.
-
#
-
# cbo = Diff::LCS::ContextDiffCallbacks.new
-
# Diff::LCS.LCS(seq1, seq2, cbo)
-
# cbo.finish
-
#
-
# Note that the call to #finish is absolutely necessary, or the last set of
-
# changes will not be visible. Alternatively, can be used as:
-
#
-
# cbo = Diff::LCS::ContextDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
-
#
-
# The necessary #finish call will be made.
-
#
-
# === Simplified Array Format
-
#
-
# The simplified array format used in the example above can be obtained
-
# with:
-
#
-
# require 'pp'
-
# pp diffs.map { |e| e.map { |f| f.to_a } }
-
1
class Diff::LCS::ContextDiffCallbacks < Diff::LCS::DiffCallbacks
-
1
def discard_a(event)
-
@hunk << Diff::LCS::ContextChange.simplify(event)
-
end
-
-
1
def discard_b(event)
-
@hunk << Diff::LCS::ContextChange.simplify(event)
-
end
-
-
1
def change(event)
-
@hunk << Diff::LCS::ContextChange.simplify(event)
-
end
-
end
-
-
# This will produce a simple array of diff change objects. Each element in
-
# the #diffs array is a single ContextChange. In the set of #diffs provided
-
# by SDiffCallbacks, both old and new objects will be presented for both
-
# changed <strong>and unchanged</strong> objects. +nil+ will be substituted
-
# for a discarded object.
-
#
-
# The diffset produced by this callback, when provided to Diff::LCS#sdiff,
-
# will compute and display the necessary components to show two sequences
-
# and their minimized differences side by side, just like the Unix utility
-
# +sdiff+.
-
#
-
# same same
-
# before | after
-
# old < -
-
# - > new
-
#
-
# seq1 = %w(a b c e h j l m n p)
-
# seq2 = %w(b c d e f j k l m r s t)
-
#
-
# diffs = Diff::LCS.sdiff(seq1, seq2)
-
# # This example shows a simplified array format.
-
# # [ [ "-", [ 0, "a"], [ 0, nil ] ],
-
# # [ "=", [ 1, "b"], [ 0, "b" ] ],
-
# # [ "=", [ 2, "c"], [ 1, "c" ] ],
-
# # [ "+", [ 3, nil], [ 2, "d" ] ],
-
# # [ "=", [ 3, "e"], [ 3, "e" ] ],
-
# # [ "!", [ 4, "h"], [ 4, "f" ] ],
-
# # [ "=", [ 5, "j"], [ 5, "j" ] ],
-
# # [ "+", [ 6, nil], [ 6, "k" ] ],
-
# # [ "=", [ 6, "l"], [ 7, "l" ] ],
-
# # [ "=", [ 7, "m"], [ 8, "m" ] ],
-
# # [ "!", [ 8, "n"], [ 9, "r" ] ],
-
# # [ "!", [ 9, "p"], [ 10, "s" ] ],
-
# # [ "+", [ 10, nil], [ 11, "t" ] ] ]
-
#
-
# The result of this operation is similar to that of
-
# Diff::LCS::ContextDiffCallbacks. They may be compared as:
-
#
-
# s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
-
# c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten
-
#
-
# s == c # -> true
-
#
-
# === Use
-
#
-
# This callback object must be initialised and is used by the Diff::LCS#sdiff
-
# method.
-
#
-
# cbo = Diff::LCS::SDiffCallbacks.new
-
# Diff::LCS.LCS(seq1, seq2, cbo)
-
#
-
# As with the other initialisable callback objects,
-
# Diff::LCS::SDiffCallbacks can be initialised with a block. As there is no
-
# "fininishing" to be done, this has no effect on the state of the object.
-
#
-
# cbo = Diff::LCS::SDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
-
#
-
# === Simplified Array Format
-
#
-
# The simplified array format used in the example above can be obtained
-
# with:
-
#
-
# require 'pp'
-
# pp diffs.map { |e| e.to_a }
-
1
class Diff::LCS::SDiffCallbacks
-
# Returns the difference set collected during the diff process.
-
1
attr_reader :diffs
-
-
1
def initialize #:yields self:
-
@diffs = []
-
yield self if block_given?
-
end
-
-
1
def match(event)
-
@diffs << Diff::LCS::ContextChange.simplify(event)
-
end
-
-
1
def discard_a(event)
-
@diffs << Diff::LCS::ContextChange.simplify(event)
-
end
-
-
1
def discard_b(event)
-
@diffs << Diff::LCS::ContextChange.simplify(event)
-
end
-
-
1
def change(event)
-
@diffs << Diff::LCS::ContextChange.simplify(event)
-
end
-
end
-
# -*- ruby encoding: utf-8 -*-
-
-
# Represents a simplistic (non-contextual) change. Represents the removal or
-
# addition of an element from either the old or the new sequenced
-
# enumerable.
-
1
class Diff::LCS::Change
-
# The only actions valid for changes are '+' (add), '-' (delete), '='
-
# (no change), '!' (changed), '<' (tail changes from first sequence), or
-
# '>' (tail changes from second sequence). The last two ('<>') are only
-
# found with Diff::LCS::diff and Diff::LCS::sdiff.
-
1
VALID_ACTIONS = %W(+ - = ! > <)
-
-
1
def self.valid_action?(action)
-
VALID_ACTIONS.include? action
-
end
-
-
# Returns the action this Change represents.
-
1
attr_reader :action
-
-
# Returns the position of the Change.
-
1
attr_reader :position
-
# Returns the sequence element of the Change.
-
1
attr_reader :element
-
-
1
def initialize(*args)
-
@action, @position, @element = *args
-
-
unless Diff::LCS::Change.valid_action?(@action)
-
raise "Invalid Change Action '#{@action}'"
-
end
-
raise "Invalid Position Type" unless @position.kind_of? Fixnum
-
end
-
-
1
def inspect
-
to_a.inspect
-
end
-
-
1
def to_a
-
[ @action, @position, @element ]
-
end
-
-
1
def self.from_a(arr)
-
arr = arr.flatten(1)
-
case arr.size
-
when 5
-
Diff::LCS::ContextChange.new(*(arr[0...5]))
-
when 3
-
Diff::LCS::Change.new(*(arr[0...3]))
-
else
-
raise "Invalid change array format provided."
-
end
-
end
-
-
1
include Comparable
-
-
1
def ==(other)
-
(self.action == other.action) and
-
(self.position == other.position) and
-
(self.element == other.element)
-
end
-
-
1
def <=>(other)
-
r = self.action <=> other.action
-
r = self.position <=> other.position if r.zero?
-
r = self.element <=> other.element if r.zero?
-
r
-
end
-
-
1
def adding?
-
@action == '+'
-
end
-
-
1
def deleting?
-
@action == '-'
-
end
-
-
1
def unchanged?
-
@action == '='
-
end
-
-
1
def changed?
-
@action == '!'
-
end
-
-
1
def finished_a?
-
@action == '>'
-
end
-
-
1
def finished_b?
-
@action == '<'
-
end
-
end
-
-
# Represents a contextual change. Contains the position and values of the
-
# elements in the old and the new sequenced enumerables as well as the action
-
# taken.
-
1
class Diff::LCS::ContextChange < Diff::LCS::Change
-
# We don't need these two values.
-
1
undef :position
-
1
undef :element
-
-
# Returns the old position being changed.
-
1
attr_reader :old_position
-
# Returns the new position being changed.
-
1
attr_reader :new_position
-
# Returns the old element being changed.
-
1
attr_reader :old_element
-
# Returns the new element being changed.
-
1
attr_reader :new_element
-
-
1
def initialize(*args)
-
@action, @old_position, @old_element, @new_position, @new_element = *args
-
-
unless Diff::LCS::Change.valid_action?(@action)
-
raise "Invalid Change Action '#{@action}'"
-
end
-
unless @old_position.nil? or @old_position.kind_of? Fixnum
-
raise "Invalid (Old) Position Type"
-
end
-
unless @new_position.nil? or @new_position.kind_of? Fixnum
-
raise "Invalid (New) Position Type"
-
end
-
end
-
-
1
def to_a
-
[ @action,
-
[ @old_position, @old_element ],
-
[ @new_position, @new_element ]
-
]
-
end
-
-
1
def inspect(*args)
-
to_a.inspect
-
end
-
-
1
def self.from_a(arr)
-
Diff::LCS::Change.from_a(arr)
-
end
-
-
# Simplifies a context change for use in some diff callbacks. '<' actions
-
# are converted to '-' and '>' actions are converted to '+'.
-
1
def self.simplify(event)
-
ea = event.to_a
-
-
case ea[0]
-
when '-'
-
ea[2][1] = nil
-
when '<'
-
ea[0] = '-'
-
ea[2][1] = nil
-
when '+'
-
ea[1][1] = nil
-
when '>'
-
ea[0] = '+'
-
ea[1][1] = nil
-
end
-
-
Diff::LCS::ContextChange.from_a(ea)
-
end
-
-
1
def ==(other)
-
(@action == other.action) and
-
(@old_position == other.old_position) and
-
(@new_position == other.new_position) and
-
(@old_element == other.old_element) and
-
(@new_element == other.new_element)
-
end
-
-
1
def <=>(other)
-
r = @action <=> other.action
-
r = @old_position <=> other.old_position if r.zero?
-
r = @new_position <=> other.new_position if r.zero?
-
r = @old_element <=> other.old_element if r.zero?
-
r = @new_element <=> other.new_element if r.zero?
-
r
-
end
-
end
-
# -*- ruby encoding: utf-8 -*-
-
-
1
require 'diff/lcs/block'
-
-
# A Hunk is a group of Blocks which overlap because of the context
-
# surrounding each block. (So if we're not using context, every hunk will
-
# contain one block.) Used in the diff program (bin/diff).
-
1
class Diff::LCS::Hunk
-
# Create a hunk using references to both the old and new data, as well as
-
# the piece of data.
-
1
def initialize(data_old, data_new, piece, flag_context, file_length_difference)
-
# At first, a hunk will have just one Block in it
-
@blocks = [ Diff::LCS::Block.new(piece) ]
-
if String.method_defined?(:encoding)
-
@preferred_data_encoding = data_old.fetch(0, data_new.fetch(0,'') ).encoding
-
end
-
@data_old = data_old
-
@data_new = data_new
-
-
before = after = file_length_difference
-
after += @blocks[0].diff_size
-
@file_length_difference = after # The caller must get this manually
-
-
# Save the start & end of each array. If the array doesn't exist (e.g.,
-
# we're only adding items in this block), then figure out the line
-
# number based on the line number of the other file and the current
-
# difference in file lengths.
-
if @blocks[0].remove.empty?
-
a1 = a2 = nil
-
else
-
a1 = @blocks[0].remove[0].position
-
a2 = @blocks[0].remove[-1].position
-
end
-
-
if @blocks[0].insert.empty?
-
b1 = b2 = nil
-
else
-
b1 = @blocks[0].insert[0].position
-
b2 = @blocks[0].insert[-1].position
-
end
-
-
@start_old = a1 || (b1 - before)
-
@start_new = b1 || (a1 + before)
-
@end_old = a2 || (b2 - after)
-
@end_new = b2 || (a2 + after)
-
-
self.flag_context = flag_context
-
end
-
-
1
attr_reader :blocks
-
1
attr_reader :start_old, :start_new
-
1
attr_reader :end_old, :end_new
-
1
attr_reader :file_length_difference
-
-
# Change the "start" and "end" fields to note that context should be added
-
# to this hunk.
-
1
attr_accessor :flag_context
-
1
undef :flag_context=;
-
1
def flag_context=(context) #:nodoc:
-
return if context.nil? or context.zero?
-
-
add_start = (context > @start_old) ? @start_old : context
-
@start_old -= add_start
-
@start_new -= add_start
-
-
if (@end_old + context) > @data_old.size
-
add_end = @data_old.size - @end_old
-
else
-
add_end = context
-
end
-
@end_old += add_end
-
@end_new += add_end
-
end
-
-
# Merges this hunk and the provided hunk together if they overlap. Returns
-
# a truthy value so that if there is no overlap, you can know the merge
-
# was skipped.
-
1
def merge(hunk)
-
if overlaps?(hunk)
-
@start_old = hunk.start_old
-
@start_new = hunk.start_new
-
blocks.unshift(*hunk.blocks)
-
else
-
nil
-
end
-
end
-
1
alias_method :unshift, :merge
-
-
# Determines whether there is an overlap between this hunk and the
-
# provided hunk. This will be true if the difference between the two hunks
-
# start or end positions is within one position of each other.
-
1
def overlaps?(hunk)
-
hunk and (((@start_old - hunk.end_old) <= 1) or
-
((@start_new - hunk.end_new) <= 1))
-
end
-
-
# Returns a diff string based on a format.
-
1
def diff(format)
-
case format
-
when :old
-
old_diff
-
when :unified
-
unified_diff
-
when :context
-
context_diff
-
when :ed
-
self
-
when :reverse_ed, :ed_finish
-
ed_diff(format)
-
else
-
raise "Unknown diff format #{format}."
-
end
-
end
-
-
# Note that an old diff can't have any context. Therefore, we know that
-
# there's only one block in the hunk.
-
1
def old_diff
-
warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
-
op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
-
-
block = @blocks[0]
-
-
# Calculate item number range. Old diff range is just like a context
-
# diff range, except the ranges are on one line with the action between
-
# them.
-
s = encode("#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n")
-
# If removing anything, just print out all the remove lines in the hunk
-
# which is just all the remove lines in the block.
-
@data_old[@start_old .. @end_old].each { |e| s << encode("< ") + e + encode("\n") } unless block.remove.empty?
-
s << encode("---\n") if block.op == "!"
-
@data_new[@start_new .. @end_new].each { |e| s << encode("> ") + e + encode("\n") } unless block.insert.empty?
-
s
-
end
-
1
private :old_diff
-
-
1
def unified_diff
-
# Calculate item number range.
-
s = encode("@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n")
-
-
# Outlist starts containing the hunk of the old file. Removing an item
-
# just means putting a '-' in front of it. Inserting an item requires
-
# getting it from the new file and splicing it in. We splice in
-
# +num_added+ items. Remove blocks use +num_added+ because splicing
-
# changed the length of outlist.
-
#
-
# We remove +num_removed+ items. Insert blocks use +num_removed+
-
# because their item numbers -- corresponding to positions in the NEW
-
# file -- don't take removed items into account.
-
lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0
-
-
outlist = @data_old[lo .. hi].map { |e| e.insert(0, encode(' ')) }
-
-
@blocks.each do |block|
-
block.remove.each do |item|
-
op = item.action.to_s # -
-
offset = item.position - lo + num_added
-
outlist[offset][0, 1] = encode(op)
-
num_removed += 1
-
end
-
block.insert.each do |item|
-
op = item.action.to_s # +
-
offset = item.position - @start_new + num_removed
-
outlist[offset, 0] = encode(op) + @data_new[item.position]
-
num_added += 1
-
end
-
end
-
-
s << outlist.join(encode("\n"))
-
end
-
1
private :unified_diff
-
-
1
def context_diff
-
s = encode("***************\n")
-
s << encode("*** #{context_range(:old)} ****\n")
-
r = context_range(:new)
-
-
# Print out file 1 part for each block in context diff format if there
-
# are any blocks that remove items
-
lo, hi = @start_old, @end_old
-
removes = @blocks.select { |e| not e.remove.empty? }
-
if removes
-
outlist = @data_old[lo .. hi].map { |e| e.insert(0, encode(' ')) }
-
-
removes.each do |block|
-
block.remove.each do |item|
-
outlist[item.position - lo][0, 1] = encode(block.op) # - or !
-
end
-
end
-
s << outlist.join("\n")
-
end
-
-
s << encode("\n--- #{r} ----\n")
-
lo, hi = @start_new, @end_new
-
inserts = @blocks.select { |e| not e.insert.empty? }
-
if inserts
-
outlist = @data_new[lo .. hi].collect { |e| e.insert(0, encode(' ')) }
-
inserts.each do |block|
-
block.insert.each do |item|
-
outlist[item.position - lo][0, 1] = encode(block.op) # + or !
-
end
-
end
-
s << outlist.join("\n")
-
end
-
s
-
end
-
1
private :context_diff
-
-
1
def ed_diff(format)
-
op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
-
warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
-
-
if format == :reverse_ed
-
s = encode("#{op_act[@blocks[0].op]}#{context_range(:old)}\n")
-
else
-
s = encode("#{context_range(:old, ' ')}#{op_act[@blocks[0].op]}\n")
-
end
-
-
unless @blocks[0].insert.empty?
-
@data_new[@start_new .. @end_new].each { |e| s << e + encode("\n") }
-
s << encode(".\n")
-
end
-
s
-
end
-
1
private :ed_diff
-
-
# Generate a range of item numbers to print. Only print 1 number if the
-
# range has only one item in it. Otherwise, it's 'start,end'
-
1
def context_range(mode, op = ',')
-
case mode
-
when :old
-
s, e = (@start_old + 1), (@end_old + 1)
-
when :new
-
s, e = (@start_new + 1), (@end_new + 1)
-
end
-
-
(s < e) ? "#{s}#{op}#{e}" : "#{e}"
-
end
-
1
private :context_range
-
-
# Generate a range of item numbers to print for unified diff. Print number
-
# where block starts, followed by number of lines in the block
-
# (don't print number of lines if it's 1)
-
1
def unified_range(mode)
-
case mode
-
when :old
-
s, e = (@start_old + 1), (@end_old + 1)
-
when :new
-
s, e = (@start_new + 1), (@end_new + 1)
-
end
-
-
length = e - s + 1
-
first = (length < 2) ? e : s # "strange, but correct"
-
(length == 1) ? "#{first}" : "#{first},#{length}"
-
end
-
1
private :unified_range
-
-
1
if String.method_defined?(:encoding)
-
1
def encode(literal, target_encoding = @preferred_data_encoding)
-
literal.encode target_encoding
-
end
-
-
1
def encode_as(string, *args)
-
args.map { |arg| arg.encode(string.encoding) }
-
end
-
else
-
def encode(literal, target_encoding = nil)
-
literal
-
end
-
def encode_as(string, *args)
-
args
-
end
-
end
-
-
1
private :encode
-
1
private :encode_as
-
end
-
# -*- ruby encoding: utf-8 -*-
-
-
1
class << Diff::LCS
-
1
def diff_traversal(method, seq1, seq2, callbacks, &block)
-
callbacks = callbacks_for(callbacks)
-
case method
-
when :diff
-
traverse_sequences(seq1, seq2, callbacks)
-
when :sdiff
-
traverse_balanced(seq1, seq2, callbacks)
-
end
-
callbacks.finish if callbacks.respond_to? :finish
-
-
if block
-
callbacks.diffs.map do |hunk|
-
if hunk.kind_of? Array
-
hunk.map { |hunk_block| block[hunk_block] }
-
else
-
block[hunk]
-
end
-
end
-
else
-
callbacks.diffs
-
end
-
end
-
1
private :diff_traversal
-
end
-
-
1
module Diff::LCS::Internals # :nodoc:
-
end
-
-
1
class << Diff::LCS::Internals
-
# Compute the longest common subsequence between the sequenced
-
# Enumerables +a+ and +b+. The result is an array whose contents is such
-
# that
-
#
-
# result = Diff::LCS::Internals.lcs(a, b)
-
# result.each_with_index do |e, i|
-
# assert_equal(a[i], b[e]) unless e.nil?
-
# end
-
1
def lcs(a, b)
-
a_start = b_start = 0
-
a_finish = a.size - 1
-
b_finish = b.size - 1
-
vector = []
-
-
# Prune off any common elements at the beginning...
-
while ((a_start <= a_finish) and (b_start <= b_finish) and
-
(a[a_start] == b[b_start]))
-
vector[a_start] = b_start
-
a_start += 1
-
b_start += 1
-
end
-
b_start = a_start
-
-
# Now the end...
-
while ((a_start <= a_finish) and (b_start <= b_finish) and
-
(a[a_finish] == b[b_finish]))
-
vector[a_finish] = b_finish
-
a_finish -= 1
-
b_finish -= 1
-
end
-
-
# Now, compute the equivalence classes of positions of elements.
-
b_matches = position_hash(b, b_start..b_finish)
-
-
thresh = []
-
links = []
-
string = a.kind_of?(String)
-
-
(a_start .. a_finish).each do |i|
-
ai = string ? a[i, 1] : a[i]
-
bm = b_matches[ai]
-
k = nil
-
bm.reverse_each do |j|
-
if k and (thresh[k] > j) and (thresh[k - 1] < j)
-
thresh[k] = j
-
else
-
k = replace_next_larger(thresh, j, k)
-
end
-
links[k] = [ (k > 0) ? links[k - 1] : nil, i, j ] unless k.nil?
-
end
-
end
-
-
unless thresh.empty?
-
link = links[thresh.size - 1]
-
while not link.nil?
-
vector[link[1]] = link[2]
-
link = link[0]
-
end
-
end
-
-
vector
-
end
-
-
# This method will analyze the provided patchset to provide a
-
# single-pass normalization (conversion of the array form of
-
# Diff::LCS::Change objects to the object form of same) and detection of
-
# whether the patchset represents changes to be made.
-
1
def analyze_patchset(patchset, depth = 0)
-
raise "Patchset too complex" if depth > 1
-
-
has_changes = false
-
-
# Format:
-
# [ # patchset
-
# # hunk (change)
-
# [ # hunk
-
# # change
-
# ]
-
# ]
-
-
patchset = patchset.map do |hunk|
-
case hunk
-
when Diff::LCS::Change
-
has_changes ||= !hunk.unchanged?
-
hunk
-
when Array
-
# Detect if the 'hunk' is actually an array-format
-
# Change object.
-
if Diff::LCS::Change.valid_action? hunk[0]
-
hunk = Diff::LCS::Change.from_a(hunk)
-
has_changes ||= !hunk.unchanged?
-
hunk
-
else
-
with_changes, hunk = analyze_patchset(hunk, depth + 1)
-
has_changes ||= with_changes
-
hunk.flatten
-
end
-
else
-
raise ArgumentError, "Cannot normalise a hunk of class #{hunk.class}."
-
end
-
end
-
-
[ has_changes, patchset.flatten(1) ]
-
end
-
-
# Examine the patchset and the source to see in which direction the
-
# patch should be applied.
-
#
-
# WARNING: By default, this examines the whole patch, so this could take
-
# some time. This also works better with Diff::LCS::ContextChange or
-
# Diff::LCS::Change as its source, as an array will cause the creation
-
# of one of the above.
-
#
-
# Note: This will be deprecated as a public function in a future release.
-
1
def intuit_diff_direction(src, patchset, limit = nil)
-
string = src.kind_of?(String)
-
count = left_match = left_miss = right_match = right_miss = 0
-
-
patchset.each do |change|
-
count += 1
-
-
case change
-
when Diff::LCS::ContextChange
-
le = string ? src[change.old_position, 1] : src[change.old_position]
-
re = string ? src[change.new_position, 1] : src[change.new_position]
-
-
case change.action
-
when '-' # Remove details from the old string
-
if le == change.old_element
-
left_match += 1
-
else
-
left_miss += 1
-
end
-
when '+'
-
if re == change.new_element
-
right_match += 1
-
else
-
right_miss += 1
-
end
-
when '='
-
left_miss += 1 if le != change.old_element
-
right_miss += 1 if re != change.new_element
-
when '!'
-
if le == change.old_element
-
left_match += 1
-
else
-
if re == change.new_element
-
right_match += 1
-
else
-
left_miss += 1
-
right_miss += 1
-
end
-
end
-
end
-
when Diff::LCS::Change
-
# With a simplistic change, we can't tell the difference between
-
# the left and right on '!' actions, so we ignore those. On '='
-
# actions, if there's a miss, we miss both left and right.
-
element = string ? src[change.position, 1] : src[change.position]
-
-
case change.action
-
when '-'
-
if element == change.element
-
left_match += 1
-
else
-
left_miss += 1
-
end
-
when '+'
-
if element == change.element
-
right_match += 1
-
else
-
right_miss += 1
-
end
-
when '='
-
if element != change.element
-
left_miss += 1
-
right_miss += 1
-
end
-
end
-
end
-
-
break if (not limit.nil?) && (count > limit)
-
end
-
-
no_left = (left_match == 0) && (left_miss > 0)
-
no_right = (right_match == 0) && (right_miss > 0)
-
-
case [no_left, no_right]
-
when [false, true]
-
:patch
-
when [true, false]
-
:unpatch
-
else
-
case left_match <=> right_match
-
when 1
-
:patch
-
when -1
-
:unpatch
-
else
-
raise "The provided patchset does not appear to apply to the provided value as either source or destination value."
-
end
-
end
-
end
-
-
# Find the place at which +value+ would normally be inserted into the
-
# Enumerable. If that place is already occupied by +value+, do nothing
-
# and return +nil+. If the place does not exist (i.e., it is off the end
-
# of the Enumerable), add it to the end. Otherwise, replace the element
-
# at that point with +value+. It is assumed that the Enumerable's values
-
# are numeric.
-
#
-
# This operation preserves the sort order.
-
1
def replace_next_larger(enum, value, last_index = nil)
-
# Off the end?
-
if enum.empty? or (value > enum[-1])
-
enum << value
-
return enum.size - 1
-
end
-
-
# Binary search for the insertion point
-
last_index ||= enum.size
-
first_index = 0
-
while (first_index <= last_index)
-
i = (first_index + last_index) >> 1
-
-
found = enum[i]
-
-
if value == found
-
return nil
-
elsif value > found
-
first_index = i + 1
-
else
-
last_index = i - 1
-
end
-
end
-
-
# The insertion point is in first_index; overwrite the next larger
-
# value.
-
enum[first_index] = value
-
return first_index
-
end
-
1
private :replace_next_larger
-
-
# If +vector+ maps the matching elements of another collection onto this
-
# Enumerable, compute the inverse of +vector+ that maps this Enumerable
-
# onto the collection. (Currently unused.)
-
1
def inverse_vector(a, vector)
-
inverse = a.dup
-
(0...vector.size).each do |i|
-
inverse[vector[i]] = i unless vector[i].nil?
-
end
-
inverse
-
end
-
1
private :inverse_vector
-
-
# Returns a hash mapping each element of an Enumerable to the set of
-
# positions it occupies in the Enumerable, optionally restricted to the
-
# elements specified in the range of indexes specified by +interval+.
-
1
def position_hash(enum, interval)
-
string = enum.kind_of?(String)
-
hash = Hash.new { |h, k| h[k] = [] }
-
interval.each do |i|
-
k = string ? enum[i, 1] : enum[i]
-
hash[k] << i
-
end
-
hash
-
end
-
1
private :position_hash
-
end
-
# encoding: utf-8
-
-
# Define equality, equivalence and inspection methods
-
1
class Equalizer < Module
-
-
# Initialize an Equalizer with the given keys
-
#
-
# Will use the keys with which it is initialized to define #cmp?,
-
# #hash, and #inspect
-
#
-
# @param [Array<Symbol>] keys
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def initialize(*keys)
-
1
@keys = keys
-
1
define_methods
-
1
freeze
-
end
-
-
1
private
-
-
# Hook called when module is included
-
#
-
# @param [Module] descendant
-
# the module or class including Equalizer
-
#
-
# @return [self]
-
#
-
# @api private
-
1
def included(descendant)
-
1
super
-
2
descendant.module_eval { include Methods }
-
end
-
-
# Define the equalizer methods based on #keys
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def define_methods
-
1
define_cmp_method
-
1
define_hash_method
-
1
define_inspect_method
-
end
-
-
# Define an #cmp? method based on the instance's values identified by #keys
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def define_cmp_method
-
1
keys = @keys
-
1
define_method(:cmp?) do |comparator, other|
-
11
keys.all? { |key| send(key).send(comparator, other.send(key)) }
-
end
-
1
private :cmp?
-
end
-
-
# Define a #hash method based on the instance's values identified by #keys
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def define_hash_method
-
1
keys = @keys
-
1
define_method(:hash) do ||
-
keys.map(&method(:send)).push(self.class).hash
-
end
-
end
-
-
# Define an inspect method that reports the values of the instance's keys
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def define_inspect_method
-
1
keys = @keys
-
1
define_method(:inspect) do ||
-
klass = self.class
-
name = klass.name || klass.inspect
-
"#<#{name}#{keys.map { |key| " #{key}=#{send(key).inspect}" }.join}>"
-
end
-
end
-
-
# The comparison methods
-
1
module Methods
-
-
# Compare the object with other object for equality
-
#
-
# @example
-
# object.eql?(other) # => true or false
-
#
-
# @param [Object] other
-
# the other object to compare with
-
#
-
# @return [Boolean]
-
#
-
# @api public
-
1
def eql?(other)
-
instance_of?(other.class) && cmp?(__method__, other)
-
end
-
-
# Compare the object with other object for equivalency
-
#
-
# @example
-
# object == other # => true or false
-
#
-
# @param [Object] other
-
# the other object to compare with
-
#
-
# @return [Boolean]
-
#
-
# @api public
-
1
def ==(other)
-
5
other = coerce(other) if respond_to?(:coerce, true)
-
5
other.kind_of?(self.class) && cmp?(__method__, other)
-
end
-
-
end # module Methods
-
end # class Equalizer
-
1
require 'thread'
-
1
require 'cgi'
-
1
require 'set'
-
1
require 'forwardable'
-
-
# Public: This is the main namespace for Faraday. You can either use it to
-
# create Faraday::Connection objects, or access it directly.
-
#
-
# Examples
-
#
-
# Faraday.get "http://faraday.com"
-
#
-
# conn = Faraday.new "http://faraday.com"
-
# conn.get '/'
-
#
-
1
module Faraday
-
1
VERSION = "0.9.0"
-
-
1
class << self
-
# Public: Gets or sets the root path that Faraday is being loaded from.
-
# This is the root from where the libraries are auto-loaded from.
-
1
attr_accessor :root_path
-
-
# Public: Gets or sets the path that the Faraday libs are loaded from.
-
1
attr_accessor :lib_path
-
-
# Public: Gets or sets the Symbol key identifying a default Adapter to use
-
# for the default Faraday::Connection.
-
1
attr_reader :default_adapter
-
-
# Public: Sets the default Faraday::Connection for simple scripts that
-
# access the Faraday constant directly.
-
#
-
# Faraday.get "https://faraday.com"
-
1
attr_writer :default_connection
-
-
# Public: Sets the default options used when calling Faraday#new.
-
1
attr_writer :default_connection_options
-
-
# Public: Initializes a new Faraday::Connection.
-
#
-
# url - The optional String base URL to use as a prefix for all
-
# requests. Can also be the options Hash.
-
# options - The optional Hash used to configure this Faraday::Connection.
-
# Any of these values will be set on every request made, unless
-
# overridden for a specific request.
-
# :url - String base URL.
-
# :params - Hash of URI query unencoded key/value pairs.
-
# :headers - Hash of unencoded HTTP header key/value pairs.
-
# :request - Hash of request options.
-
# :ssl - Hash of SSL options.
-
# :proxy - Hash of Proxy options.
-
#
-
# Examples
-
#
-
# Faraday.new 'http://faraday.com'
-
#
-
# # http://faraday.com?page=1
-
# Faraday.new 'http://faraday.com', :params => {:page => 1}
-
#
-
# # same
-
#
-
# Faraday.new :url => 'http://faraday.com',
-
# :params => {:page => 1}
-
#
-
# Returns a Faraday::Connection.
-
1
def new(url = nil, options = nil)
-
17
block = block_given? ? Proc.new : nil
-
17
options = options ? default_connection_options.merge(options) : default_connection_options.dup
-
17
Faraday::Connection.new(url, options, &block)
-
end
-
-
# Internal: Requires internal Faraday libraries.
-
#
-
# *libs - One or more relative String names to Faraday classes.
-
#
-
# Returns nothing.
-
1
def require_libs(*libs)
-
3
libs.each do |lib|
-
13
require "#{lib_path}/#{lib}"
-
end
-
end
-
-
# Public: Updates default adapter while resetting
-
# #default_connection.
-
#
-
# Returns the new default_adapter.
-
1
def default_adapter=(adapter)
-
1
@default_connection = nil
-
1
@default_adapter = adapter
-
end
-
-
1
alias require_lib require_libs
-
-
1
private
-
# Internal: Proxies method calls on the Faraday constant to
-
# #default_connection.
-
1
def method_missing(name, *args, &block)
-
default_connection.send(name, *args, &block)
-
end
-
end
-
-
1
self.root_path = File.expand_path "..", __FILE__
-
1
self.lib_path = File.expand_path "../faraday", __FILE__
-
1
self.default_adapter = :net_http
-
-
# Gets the default connection used for simple scripts.
-
#
-
# Returns a Faraday::Connection, configured with the #default_adapter.
-
1
def self.default_connection
-
@default_connection ||= Connection.new
-
end
-
-
# Gets the default connection options used when calling Faraday#new.
-
#
-
# Returns a Faraday::ConnectionOptions.
-
1
def self.default_connection_options
-
17
@default_connection_options ||= ConnectionOptions.new
-
end
-
-
1
if (!defined?(RUBY_ENGINE) || "ruby" == RUBY_ENGINE) && RUBY_VERSION < '1.9'
-
begin
-
require 'system_timer'
-
Timer = SystemTimer
-
rescue LoadError
-
warn "Faraday: you may want to install system_timer for reliable timeouts"
-
end
-
end
-
-
1
unless const_defined? :Timer
-
1
require 'timeout'
-
1
Timer = Timeout
-
end
-
-
# Public: Adds the ability for other modules to register and lookup
-
# middleware classes.
-
1
module MiddlewareRegistry
-
# Public: Register middleware class(es) on the current module.
-
#
-
# mapping - A Hash mapping Symbol keys to classes. Classes can be expressed
-
# as fully qualified constant, or a Proc that will be lazily
-
# called to return the former.
-
#
-
# Examples
-
#
-
# module Faraday
-
# class Whatever
-
# # Middleware looked up by :foo returns Faraday::Whatever::Foo.
-
# register_middleware :foo => Foo
-
#
-
# # Middleware looked up by :bar returns Faraday::Whatever.const_get(:Bar)
-
# register_middleware :bar => :Bar
-
#
-
# # Middleware looked up by :baz requires 'baz' and returns Faraday::Whatever.const_get(:Baz)
-
# register_middleware :baz => [:Baz, 'baz']
-
# end
-
# end
-
#
-
# Returns nothing.
-
1
def register_middleware(autoload_path = nil, mapping = nil)
-
5
if mapping.nil?
-
2
mapping = autoload_path
-
2
autoload_path = nil
-
end
-
5
middleware_mutex do
-
5
@middleware_autoload_path = autoload_path if autoload_path
-
5
(@registered_middleware ||= {}).update(mapping)
-
end
-
end
-
-
# Public: Lookup middleware class with a registered Symbol shortcut.
-
#
-
# key - The Symbol key for the registered middleware.
-
#
-
# Examples
-
#
-
# module Faraday
-
# class Whatever
-
# register_middleware :foo => Foo
-
# end
-
# end
-
#
-
# Faraday::Whatever.lookup_middleware(:foo)
-
# # => Faraday::Whatever::Foo
-
#
-
# Returns a middleware Class.
-
1
def lookup_middleware(key)
-
load_middleware(key) ||
-
90
raise(Faraday::Error.new("#{key.inspect} is not registered on #{self}"))
-
end
-
-
1
def middleware_mutex(&block)
-
@middleware_mutex ||= begin
-
3
require 'monitor'
-
3
Monitor.new
-
11
end
-
11
@middleware_mutex.synchronize(&block)
-
end
-
-
1
def fetch_middleware(key)
-
93
defined?(@registered_middleware) && @registered_middleware[key]
-
end
-
-
1
def load_middleware(key)
-
93
value = fetch_middleware(key)
-
93
case value
-
when Module
-
87
value
-
when Symbol, String
-
3
middleware_mutex do
-
3
@registered_middleware[key] = const_get(value)
-
end
-
when Proc
-
middleware_mutex do
-
@registered_middleware[key] = value.call
-
end
-
when Array
-
3
middleware_mutex do
-
3
const, path = value
-
3
if root = @middleware_autoload_path
-
3
path = "#{root}/#{path}"
-
end
-
3
require(path)
-
3
@registered_middleware[key] = const
-
end
-
3
load_middleware(key)
-
end
-
end
-
end
-
-
1
def self.const_missing(name)
-
if name.to_sym == :Builder
-
warn "Faraday::Builder is now Faraday::RackBuilder."
-
const_set name, RackBuilder
-
else
-
super
-
end
-
end
-
-
1
require_libs "utils", "options", "connection", "rack_builder", "parameters",
-
"middleware", "adapter", "request", "response", "upload_io", "error"
-
-
1
if !ENV["FARADAY_NO_AUTOLOAD"]
-
1
require_lib 'autoload'
-
end
-
end
-
-
# not pulling in active-support JUST for this method. And I love this method.
-
1
class Object
-
# The primary purpose of this method is to "tap into" a method chain,
-
# in order to perform operations on intermediate results within the chain.
-
#
-
# Examples
-
#
-
# (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
-
# tap { |x| puts "array: #{x.inspect}" }.
-
# select { |x| x%2 == 0 }.
-
# tap { |x| puts "evens: #{x.inspect}" }.
-
# map { |x| x*x }.
-
# tap { |x| puts "squares: #{x.inspect}" }
-
#
-
# Yields self.
-
# Returns self.
-
def tap
-
yield(self)
-
self
-
1
end unless Object.respond_to?(:tap)
-
end
-
1
module Faraday
-
# Public: This is a base class for all Faraday adapters. Adapters are
-
# responsible for fulfilling a Faraday request.
-
1
class Adapter < Middleware
-
1
CONTENT_LENGTH = 'Content-Length'.freeze
-
-
1
register_middleware File.expand_path('../adapter', __FILE__),
-
:test => [:Test, 'test'],
-
:net_http => [:NetHttp, 'net_http'],
-
:net_http_persistent => [:NetHttpPersistent, 'net_http_persistent'],
-
:typhoeus => [:Typhoeus, 'typhoeus'],
-
:patron => [:Patron, 'patron'],
-
:em_synchrony => [:EMSynchrony, 'em_synchrony'],
-
:em_http => [:EMHttp, 'em_http'],
-
:excon => [:Excon, 'excon'],
-
:rack => [:Rack, 'rack'],
-
:httpclient => [:HTTPClient, 'httpclient']
-
-
# Public: This module marks an Adapter as supporting parallel requests.
-
1
module Parallelism
-
1
attr_writer :supports_parallel
-
2
def supports_parallel?() @supports_parallel end
-
-
1
def inherited(subclass)
-
1
super
-
1
subclass.supports_parallel = self.supports_parallel?
-
end
-
end
-
-
1
extend Parallelism
-
1
self.supports_parallel = false
-
-
1
def call(env)
-
15
env.clear_body if env.needs_body?
-
end
-
-
1
def save_response(env, status, body, headers = nil)
-
15
env.status = status
-
15
env.body = body
-
15
env.response_headers = Utils::Headers.new.tap do |response_headers|
-
15
response_headers.update headers unless headers.nil?
-
15
yield(response_headers) if block_given?
-
end
-
end
-
end
-
end
-
1
begin
-
1
require 'net/https'
-
rescue LoadError
-
warn "Warning: no such file to load -- net/https. Make sure openssl is installed if you want ssl support"
-
require 'net/http'
-
end
-
1
require 'zlib'
-
-
1
module Faraday
-
1
class Adapter
-
1
class NetHttp < Faraday::Adapter
-
1
NET_HTTP_EXCEPTIONS = [
-
EOFError,
-
Errno::ECONNABORTED,
-
Errno::ECONNREFUSED,
-
Errno::ECONNRESET,
-
Errno::EHOSTUNREACH,
-
Errno::EINVAL,
-
Errno::ENETUNREACH,
-
Net::HTTPBadResponse,
-
Net::HTTPHeaderSyntaxError,
-
Net::ProtocolError,
-
SocketError,
-
Zlib::GzipFile::Error,
-
]
-
-
1
NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL)
-
-
1
def call(env)
-
15
super
-
15
http = net_http_connection(env)
-
15
configure_ssl(http, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
-
-
15
req = env[:request]
-
15
http.read_timeout = http.open_timeout = req[:timeout] if req[:timeout]
-
15
http.open_timeout = req[:open_timeout] if req[:open_timeout]
-
-
15
begin
-
15
http_response = perform_request(http, env)
-
rescue *NET_HTTP_EXCEPTIONS => err
-
if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
-
raise Faraday::SSLError, err
-
else
-
raise Error::ConnectionFailed, err
-
end
-
end
-
-
15
save_response(env, http_response.code.to_i, http_response.body || '') do |response_headers|
-
15
http_response.each_header do |key, value|
-
15
response_headers[key] = value
-
end
-
end
-
-
15
@app.call env
-
rescue Timeout::Error => err
-
raise Faraday::Error::TimeoutError, err
-
end
-
-
1
def create_request(env)
-
request = Net::HTTPGenericRequest.new \
-
env[:method].to_s.upcase, # request method
-
!!env[:body], # is there request body
-
:head != env[:method], # is there response body
-
env[:url].request_uri, # request uri path
-
env[:request_headers] # request headers
-
-
if env[:body].respond_to?(:read)
-
request.body_stream = env[:body]
-
else
-
request.body = env[:body]
-
end
-
request
-
end
-
-
1
def perform_request(http, env)
-
15
if :get == env[:method] and !env[:body]
-
# prefer `get` to `request` because the former handles gzip (ruby 1.9)
-
15
http.get env[:url].request_uri, env[:request_headers]
-
else
-
http.request create_request(env)
-
end
-
end
-
-
1
def net_http_connection(env)
-
if proxy = env[:request][:proxy]
-
Net::HTTP::Proxy(proxy[:uri].host, proxy[:uri].port, proxy[:user], proxy[:password])
-
else
-
15
Net::HTTP
-
15
end.new(env[:url].host, env[:url].port)
-
end
-
-
1
def configure_ssl(http, ssl)
-
15
http.use_ssl = true
-
15
http.verify_mode = ssl_verify_mode(ssl)
-
15
http.cert_store = ssl_cert_store(ssl)
-
-
15
http.cert = ssl[:client_cert] if ssl[:client_cert]
-
15
http.key = ssl[:client_key] if ssl[:client_key]
-
15
http.ca_file = ssl[:ca_file] if ssl[:ca_file]
-
15
http.ca_path = ssl[:ca_path] if ssl[:ca_path]
-
15
http.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
-
15
http.ssl_version = ssl[:version] if ssl[:version]
-
end
-
-
1
def ssl_cert_store(ssl)
-
15
return ssl[:cert_store] if ssl[:cert_store]
-
# Use the default cert store by default, i.e. system ca certs
-
15
cert_store = OpenSSL::X509::Store.new
-
15
cert_store.set_default_paths
-
15
cert_store
-
end
-
-
1
def ssl_verify_mode(ssl)
-
ssl[:verify_mode] || begin
-
15
if ssl.fetch(:verify, true)
-
15
OpenSSL::SSL::VERIFY_PEER
-
else
-
OpenSSL::SSL::VERIFY_NONE
-
end
-
15
end
-
end
-
end
-
end
-
end
-
1
module Faraday
-
# Internal: Adds the ability for other modules to manage autoloadable
-
# constants.
-
1
module AutoloadHelper
-
# Internal: Registers the constants to be auto loaded.
-
#
-
# prefix - The String require prefix. If the path is inside Faraday, then
-
# it will be prefixed with the root path of this loaded Faraday
-
# version.
-
# options - Hash of Symbol => String library names.
-
#
-
# Examples.
-
#
-
# Faraday.autoload_all 'faraday/foo',
-
# :Bar => 'bar'
-
#
-
# # requires faraday/foo/bar to load Faraday::Bar.
-
# Faraday::Bar
-
#
-
#
-
# Returns nothing.
-
1
def autoload_all(prefix, options)
-
3
if prefix =~ /^faraday(\/|$)/i
-
3
prefix = File.join(Faraday.root_path, prefix)
-
end
-
3
options.each do |const_name, path|
-
20
autoload const_name, File.join(prefix, path)
-
end
-
end
-
-
# Internal: Loads each autoloaded constant. If thread safety is a concern,
-
# wrap this in a Mutex.
-
#
-
# Returns nothing.
-
1
def load_autoloaded_constants
-
constants.each do |const|
-
const_get(const) if autoload?(const)
-
end
-
end
-
-
# Internal: Filters the module's contents with those that have been already
-
# autoloaded.
-
#
-
# Returns an Array of Class/Module objects.
-
1
def all_loaded_constants
-
constants.map { |c| const_get(c) }.
-
select { |a| a.respond_to?(:loaded?) && a.loaded? }
-
end
-
end
-
-
1
class Adapter
-
1
extend AutoloadHelper
-
1
autoload_all 'faraday/adapter',
-
:NetHttp => 'net_http',
-
:NetHttpPersistent => 'net_http_persistent',
-
:Typhoeus => 'typhoeus',
-
:EMSynchrony => 'em_synchrony',
-
:EMHttp => 'em_http',
-
:Patron => 'patron',
-
:Excon => 'excon',
-
:Test => 'test',
-
:Rack => 'rack',
-
:HTTPClient => 'httpclient'
-
end
-
-
1
class Request
-
1
extend AutoloadHelper
-
1
autoload_all 'faraday/request',
-
:UrlEncoded => 'url_encoded',
-
:Multipart => 'multipart',
-
:Retry => 'retry',
-
:Timeout => 'timeout',
-
:Authorization => 'authorization',
-
:BasicAuthentication => 'basic_authentication',
-
:TokenAuthentication => 'token_authentication',
-
:Instrumentation => 'instrumentation'
-
end
-
-
1
class Response
-
1
extend AutoloadHelper
-
1
autoload_all 'faraday/response',
-
:RaiseError => 'raise_error',
-
:Logger => 'logger'
-
end
-
end
-
1
module Faraday
-
# Public: Connection objects manage the default properties and the middleware
-
# stack for fulfilling an HTTP request.
-
#
-
# Examples
-
#
-
# conn = Faraday::Connection.new 'http://sushi.com'
-
#
-
# # GET http://sushi.com/nigiri
-
# conn.get 'nigiri'
-
# # => #<Faraday::Response>
-
#
-
1
class Connection
-
# A Set of allowed HTTP verbs.
-
1
METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
-
-
# Public: Returns a Hash of URI query unencoded key/value pairs.
-
1
attr_reader :params
-
-
# Public: Returns a Hash of unencoded HTTP header key/value pairs.
-
1
attr_reader :headers
-
-
# Public: Returns a URI with the prefix used for all requests from this
-
# Connection. This includes a default host name, scheme, port, and path.
-
1
attr_reader :url_prefix
-
-
# Public: Returns the Faraday::Builder for this Connection.
-
1
attr_reader :builder
-
-
# Public: Returns a Hash of the request options.
-
1
attr_reader :options
-
-
# Public: Returns a Hash of the SSL options.
-
1
attr_reader :ssl
-
-
# Public: Returns the parallel manager for this Connection.
-
1
attr_reader :parallel_manager
-
-
# Public: Sets the default parallel manager for this connection.
-
1
attr_writer :default_parallel_manager
-
-
# Public: Initializes a new Faraday::Connection.
-
#
-
# url - URI or String base URL to use as a prefix for all
-
# requests (optional).
-
# options - Hash or Faraday::ConnectionOptions.
-
# :url - URI or String base URL (default: "http:/").
-
# :params - Hash of URI query unencoded key/value pairs.
-
# :headers - Hash of unencoded HTTP header key/value pairs.
-
# :request - Hash of request options.
-
# :ssl - Hash of SSL options.
-
# :proxy - URI, String or Hash of HTTP proxy options
-
# (default: "http_proxy" environment variable).
-
# :uri - URI or String
-
# :user - String (optional)
-
# :password - String (optional)
-
1
def initialize(url = nil, options = nil)
-
17
if url.is_a?(Hash)
-
options = ConnectionOptions.from(url)
-
url = options.url
-
else
-
17
options = ConnectionOptions.from(options)
-
end
-
-
17
@parallel_manager = nil
-
17
@headers = Utils::Headers.new
-
17
@params = Utils::ParamsHash.new
-
17
@options = options.request
-
17
@ssl = options.ssl
-
17
@default_parallel_manager = options.parallel_manager
-
-
17
@builder = options.builder || begin
-
# pass an empty block to Builder so it doesn't assume default middleware
-
options.new_builder(block_given? ? Proc.new { |b| } : nil)
-
end
-
-
17
self.url_prefix = url || 'http:/'
-
-
17
@params.update(options.params) if options.params
-
17
@headers.update(options.headers) if options.headers
-
-
17
@proxy = nil
-
17
proxy(options.fetch(:proxy) {
-
17
uri = ENV['http_proxy']
-
17
if uri && !uri.empty?
-
uri = 'http://' + uri if uri !~ /^http/i
-
uri
-
end
-
})
-
-
17
yield(self) if block_given?
-
-
17
@headers[:user_agent] ||= "Faraday v#{VERSION}"
-
end
-
-
# Public: Sets the Hash of URI query unencoded key/value pairs.
-
1
def params=(hash)
-
@params.replace hash
-
end
-
-
# Public: Sets the Hash of unencoded HTTP header key/value pairs.
-
1
def headers=(hash)
-
@headers.replace hash
-
end
-
-
1
extend Forwardable
-
-
1
def_delegators :builder, :build, :use, :request, :response, :adapter, :app
-
-
# Public: Makes an HTTP request without a body.
-
#
-
# url - The optional String base URL to use as a prefix for all
-
# requests. Can also be the options Hash.
-
# params - Hash of URI query unencoded key/value pairs.
-
# headers - Hash of unencoded HTTP header key/value pairs.
-
#
-
# Examples
-
#
-
# conn.get '/items', {:page => 1}, :accept => 'application/json'
-
# conn.head '/items/1'
-
#
-
# # ElasticSearch example sending a body with GET.
-
# conn.get '/twitter/tweet/_search' do |req|
-
# req.headers[:content_type] = 'application/json'
-
# req.params[:routing] = 'kimchy'
-
# req.body = JSON.generate(:query => {...})
-
# end
-
#
-
# Yields a Faraday::Response for further request customizations.
-
# Returns a Faraday::Response.
-
#
-
# Signature
-
#
-
# <verb>(url = nil, params = nil, headers = nil)
-
#
-
# verb - An HTTP verb: get, head, or delete.
-
1
%w[get head delete].each do |method|
-
3
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{method}(url = nil, params = nil, headers = nil)
-
run_request(:#{method}, url, nil, headers) { |request|
-
request.params.update(params) if params
-
yield(request) if block_given?
-
}
-
end
-
RUBY
-
end
-
-
# Public: Makes an HTTP request with a body.
-
#
-
# url - The optional String base URL to use as a prefix for all
-
# requests. Can also be the options Hash.
-
# body - The String body for the request.
-
# headers - Hash of unencoded HTTP header key/value pairs.
-
#
-
# Examples
-
#
-
# conn.post '/items', data, :content_type => 'application/json'
-
#
-
# # Simple ElasticSearch indexing sample.
-
# conn.post '/twitter/tweet' do |req|
-
# req.headers[:content_type] = 'application/json'
-
# req.params[:routing] = 'kimchy'
-
# req.body = JSON.generate(:user => 'kimchy', ...)
-
# end
-
#
-
# Yields a Faraday::Response for further request customizations.
-
# Returns a Faraday::Response.
-
#
-
# Signature
-
#
-
# <verb>(url = nil, body = nil, headers = nil)
-
#
-
# verb - An HTTP verb: post, put, or patch.
-
1
%w[post put patch].each do |method|
-
3
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{method}(url = nil, body = nil, headers = nil, &block)
-
run_request(:#{method}, url, body, headers, &block)
-
end
-
RUBY
-
end
-
-
# Public: Sets up the Authorization header with these credentials, encoded
-
# with base64.
-
#
-
# login - The authentication login.
-
# pass - The authentication password.
-
#
-
# Examples
-
#
-
# conn.basic_auth 'Aladdin', 'open sesame'
-
# conn.headers['Authorization']
-
# # => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
-
#
-
# Returns nothing.
-
1
def basic_auth(login, pass)
-
set_authorization_header(:basic_auth, login, pass)
-
end
-
-
# Public: Sets up the Authorization header with the given token.
-
#
-
# token - The String token.
-
# options - Optional Hash of extra token options.
-
#
-
# Examples
-
#
-
# conn.token_auth 'abcdef', :foo => 'bar'
-
# conn.headers['Authorization']
-
# # => "Token token=\"abcdef\",
-
# foo=\"bar\""
-
#
-
# Returns nothing.
-
1
def token_auth(token, options = nil)
-
set_authorization_header(:token_auth, token, options)
-
end
-
-
# Public: Sets up a custom Authorization header.
-
#
-
# type - The String authorization type.
-
# token - The String or Hash token. A String value is taken literally, and
-
# a Hash is encoded into comma separated key/value pairs.
-
#
-
# Examples
-
#
-
# conn.authorization :Bearer, 'mF_9.B5f-4.1JqM'
-
# conn.headers['Authorization']
-
# # => "Bearer mF_9.B5f-4.1JqM"
-
#
-
# conn.authorization :Token, :token => 'abcdef', :foo => 'bar'
-
# conn.headers['Authorization']
-
# # => "Token token=\"abcdef\",
-
# foo=\"bar\""
-
#
-
# Returns nothing.
-
1
def authorization(type, token)
-
set_authorization_header(:authorization, type, token)
-
end
-
-
# Internal: Traverse the middleware stack in search of a
-
# parallel-capable adapter.
-
#
-
# Yields in case of not found.
-
#
-
# Returns a parallel manager or nil if not found.
-
1
def default_parallel_manager
-
@default_parallel_manager ||= begin
-
handler = @builder.handlers.detect do |h|
-
h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel?
-
end
-
-
if handler
-
handler.klass.setup_parallel_manager
-
elsif block_given?
-
yield
-
end
-
end
-
end
-
-
# Public: Determine if this Faraday::Connection can make parallel requests.
-
#
-
# Returns true or false.
-
1
def in_parallel?
-
!!@parallel_manager
-
end
-
-
# Public: Sets up the parallel manager to make a set of requests.
-
#
-
# manager - The parallel manager that this Connection's Adapter uses.
-
#
-
# Yields a block to execute multiple requests.
-
# Returns nothing.
-
1
def in_parallel(manager = nil)
-
@parallel_manager = manager || default_parallel_manager {
-
warn "Warning: `in_parallel` called but no parallel-capable adapter on Faraday stack"
-
warn caller[2,10].join("\n")
-
nil
-
}
-
yield
-
@parallel_manager && @parallel_manager.run
-
ensure
-
@parallel_manager = nil
-
end
-
-
# Public: Gets or Sets the Hash proxy options.
-
1
def proxy(arg = nil)
-
32
return @proxy if arg.nil?
-
@proxy = ProxyOptions.from(arg)
-
end
-
-
1
def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
-
1
def_delegator :url_prefix, :path, :path_prefix
-
-
# Public: Parses the giving url with URI and stores the individual
-
# components in this connection. These components serve as defaults for
-
# requests made by this connection.
-
#
-
# url - A String or URI.
-
#
-
# Examples
-
#
-
# conn = Faraday::Connection.new { ... }
-
# conn.url_prefix = "https://sushi.com/api"
-
# conn.scheme # => https
-
# conn.path_prefix # => "/api"
-
#
-
# conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
-
#
-
# Returns the parsed URI from teh given input..
-
1
def url_prefix=(url, encoder = nil)
-
17
uri = @url_prefix = Utils.URI(url)
-
17
self.path_prefix = uri.path
-
-
17
params.merge_query(uri.query, encoder)
-
17
uri.query = nil
-
-
17
with_uri_credentials(uri) do |user, password|
-
basic_auth user, password
-
uri.user = uri.password = nil
-
end
-
-
17
uri
-
end
-
-
# Public: Sets the path prefix and ensures that it always has a leading
-
# slash.
-
#
-
# value - A String.
-
#
-
# Returns the new String path prefix.
-
1
def path_prefix=(value)
-
17
url_prefix.path = if value
-
17
value = '/' + value unless value[0,1] == '/'
-
17
value
-
end
-
end
-
-
# Public: Takes a relative url for a request and combines it with the defaults
-
# set on the connection instance.
-
#
-
# conn = Faraday::Connection.new { ... }
-
# conn.url_prefix = "https://sushi.com/api?token=abc"
-
# conn.scheme # => https
-
# conn.path_prefix # => "/api"
-
#
-
# conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
-
# conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
-
#
-
1
def build_url(url = nil, extra_params = nil)
-
uri = build_exclusive_url(url)
-
-
query_values = params.dup.merge_query(uri.query, options.params_encoder)
-
query_values.update extra_params if extra_params
-
uri.query = query_values.empty? ? nil : query_values.to_query(options.params_encoder)
-
-
uri
-
end
-
-
# Builds and runs the Faraday::Request.
-
#
-
# method - The Symbol HTTP method.
-
# url - The String or URI to access.
-
# body - The String body
-
# headers - Hash of unencoded HTTP header key/value pairs.
-
#
-
# Returns a Faraday::Response.
-
1
def run_request(method, url, body, headers)
-
15
if !METHODS.include?(method)
-
raise ArgumentError, "unknown http method: #{method}"
-
end
-
-
15
request = build_request(method) do |req|
-
15
req.url(url) if url
-
15
req.headers.update(headers) if headers
-
15
req.body = body if body
-
15
yield(req) if block_given?
-
end
-
-
15
builder.build_response(self, request)
-
end
-
-
# Creates and configures the request object.
-
#
-
# Returns the new Request.
-
1
def build_request(method)
-
15
Request.create(method) do |req|
-
15
req.params = self.params.dup
-
15
req.headers = self.headers.dup
-
15
req.options = self.options.merge(:proxy => self.proxy)
-
15
yield(req) if block_given?
-
end
-
end
-
-
# Internal: Build an absolute URL based on url_prefix.
-
#
-
# url - A String or URI-like object
-
# params - A Faraday::Utils::ParamsHash to replace the query values
-
# of the resulting url (default: nil).
-
#
-
# Returns the resulting URI instance.
-
1
def build_exclusive_url(url = nil, params = nil)
-
15
url = nil if url.respond_to?(:empty?) and url.empty?
-
15
base = url_prefix
-
15
if url and base.path and base.path !~ /\/$/
-
base = base.dup
-
base.path = base.path + '/' # ensure trailing slash
-
end
-
15
uri = url ? base + url : base
-
15
uri.query = params.to_query(options.params_encoder) if params
-
15
uri.query = nil if uri.query and uri.query.empty?
-
15
uri
-
end
-
-
# Internal: Creates a duplicate of this Faraday::Connection.
-
#
-
# Returns a Faraday::Connection.
-
1
def dup
-
self.class.new(build_exclusive_url, :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup)
-
end
-
-
# Internal: Yields username and password extracted from a URI if they both exist.
-
1
def with_uri_credentials(uri)
-
17
if uri.user and uri.password
-
yield(Utils.unescape(uri.user), Utils.unescape(uri.password))
-
end
-
end
-
-
1
def set_authorization_header(header_type, *args)
-
header = Faraday::Request.lookup_middleware(header_type).
-
header(*args)
-
headers[Faraday::Request::Authorization::KEY] = header
-
end
-
end
-
end
-
1
module Faraday
-
1
class Error < StandardError; end
-
1
class MissingDependency < Error; end
-
-
1
class ClientError < Error
-
1
attr_reader :response
-
-
1
def initialize(ex, response = nil)
-
2
@wrapped_exception = nil
-
2
@response = response
-
-
2
if ex.respond_to?(:backtrace)
-
super(ex.message)
-
@wrapped_exception = ex
-
2
elsif ex.respond_to?(:each_key)
-
super("the server responded with status #{ex[:status]}")
-
@response = ex
-
else
-
2
super(ex.to_s)
-
end
-
end
-
-
1
def backtrace
-
2
if @wrapped_exception
-
@wrapped_exception.backtrace
-
else
-
2
super
-
end
-
end
-
-
1
def inspect
-
%(#<#{self.class}>)
-
end
-
end
-
-
1
class ConnectionFailed < ClientError; end
-
1
class ResourceNotFound < ClientError; end
-
1
class ParsingError < ClientError; end
-
-
1
class TimeoutError < ClientError
-
1
def initialize(ex = nil)
-
1
super(ex || "timeout")
-
end
-
end
-
-
1
class SSLError < ClientError
-
end
-
-
[:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound,
-
1
:ParsingError, :TimeoutError, :SSLError].each do |const|
-
7
Error.const_set(const, Faraday.const_get(const))
-
end
-
end
-
1
module Faraday
-
1
class Middleware
-
1
extend MiddlewareRegistry
-
-
1
class << self
-
1
attr_accessor :load_error
-
1
private :load_error=
-
end
-
-
1
self.load_error = nil
-
-
# Executes a block which should try to require and reference dependent libraries
-
1
def self.dependency(lib = nil)
-
lib ? require(lib) : yield
-
rescue LoadError, NameError => error
-
self.load_error = error
-
end
-
-
1
def self.new(*)
-
75
raise "missing dependency for #{self}: #{load_error.message}" unless loaded?
-
75
super
-
end
-
-
1
def self.loaded?
-
75
load_error.nil?
-
end
-
-
1
def self.inherited(subclass)
-
7
super
-
7
subclass.send(:load_error=, self.load_error)
-
end
-
-
1
def initialize(app = nil)
-
75
@app = app
-
end
-
end
-
end
-
1
module Faraday
-
# Subclasses Struct with some special helpers for converting from a Hash to
-
# a Struct.
-
1
class Options < Struct
-
# Public
-
1
def self.from(value)
-
98
value ? new.update(value) : new
-
end
-
-
# Public
-
1
def each
-
81
return to_enum(:each) unless block_given?
-
81
members.each do |key|
-
729
yield(key.to_sym, send(key))
-
end
-
end
-
-
# Public
-
1
def update(obj)
-
130
obj.each do |key, value|
-
846
if sub_options = self.class.options_for(key)
-
96
value = sub_options.from(value) if value
-
elsif Hash === value
-
34
hash = {}
-
34
value.each do |hash_key, hash_value|
-
68
hash[hash_key] = hash_value
-
end
-
34
value = hash
-
end
-
-
846
self.send("#{key}=", value) unless value.nil?
-
end
-
130
self
-
end
-
-
1
alias merge! update
-
-
# Public
-
1
def delete(key)
-
value = send(key)
-
send("#{key}=", nil)
-
value
-
end
-
-
# Public
-
1
def clear
-
members.each { |member| delete(member) }
-
end
-
-
# Public
-
1
def merge(value)
-
32
dup.update(value)
-
end
-
-
# Public
-
1
def fetch(key, *args)
-
32
unless symbolized_key_set.include?(key.to_sym)
-
32
key_setter = "#{key}="
-
32
if args.size > 0
-
15
send(key_setter, args.first)
-
elsif block_given?
-
17
send(key_setter, Proc.new.call(key))
-
else
-
raise self.class.fetch_error_class, "key not found: #{key.inspect}"
-
end
-
end
-
32
send(key)
-
end
-
-
# Public
-
1
def values_at(*keys)
-
keys.map { |key| send(key) }
-
end
-
-
# Public
-
1
def keys
-
350
members.reject { |member| send(member).nil? }
-
end
-
-
# Public
-
1
def empty?
-
keys.empty?
-
end
-
-
# Public
-
1
def each_key
-
return to_enum(:each_key) unless block_given?
-
keys.each do |key|
-
yield(key)
-
end
-
end
-
-
# Public
-
1
def key?(key)
-
keys.include?(key)
-
end
-
-
1
alias has_key? key?
-
-
# Public
-
1
def each_value
-
return to_enum(:each_value) unless block_given?
-
values.each do |value|
-
yield(value)
-
end
-
end
-
-
# Public
-
1
def value?(value)
-
values.include?(value)
-
end
-
-
1
alias has_value? value?
-
-
# Public
-
1
def to_hash
-
hash = {}
-
members.each do |key|
-
value = send(key)
-
hash[key.to_sym] = value unless value.nil?
-
end
-
hash
-
end
-
-
# Internal
-
1
def inspect
-
values = []
-
members.each do |member|
-
value = send(member)
-
values << "#{member}=#{value.inspect}" if value
-
end
-
values = values.empty? ? ' (empty)' : (' ' << values.join(", "))
-
-
%(#<#{self.class}#{values}>)
-
end
-
-
# Internal
-
1
def self.options(mapping)
-
2
attribute_options.update(mapping)
-
end
-
-
# Internal
-
1
def self.options_for(key)
-
863
attribute_options[key]
-
end
-
-
# Internal
-
1
def self.attribute_options
-
885
@attribute_options ||= {}
-
end
-
-
1
def self.memoized(key)
-
5
memoized_attributes[key.to_sym] = Proc.new
-
5
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{key}() self[:#{key}]; end
-
RUBY
-
end
-
-
1
def self.memoized_attributes
-
521
@memoized_attributes ||= {}
-
end
-
-
1
def [](key)
-
496
key = key.to_sym
-
496
if method = self.class.memoized_attributes[key]
-
136
super(key) || (self[key] = instance_eval(&method))
-
else
-
360
super
-
end
-
end
-
-
1
def symbolized_key_set
-
117
@symbolized_key_set ||= Set.new(keys.map { |k| k.to_sym })
-
end
-
-
1
def self.inherited(subclass)
-
10
super
-
10
subclass.attribute_options.update(attribute_options)
-
10
subclass.memoized_attributes.update(memoized_attributes)
-
end
-
-
1
def self.fetch_error_class
-
@fetch_error_class ||= if Object.const_defined?(:KeyError)
-
::KeyError
-
else
-
::IndexError
-
end
-
end
-
end
-
-
class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
-
:timeout, :open_timeout, :boundary,
-
1
:oauth)
-
-
1
def []=(key, value)
-
if key && key.to_sym == :proxy
-
super(key, value ? ProxyOptions.from(value) : nil)
-
else
-
super(key, value)
-
end
-
end
-
end
-
-
class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
-
1
:cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth, :version)
-
-
1
def verify?
-
verify != false
-
end
-
-
1
def disable?
-
!verify?
-
end
-
end
-
-
1
class ProxyOptions < Options.new(:uri, :user, :password)
-
1
extend Forwardable
-
1
def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path=
-
-
1
def self.from(value)
-
case value
-
when String
-
value = {:uri => Utils.URI(value)}
-
when URI
-
value = {:uri => value}
-
when Hash, Options
-
if uri = value.delete(:uri)
-
value[:uri] = Utils.URI(uri)
-
end
-
end
-
super(value)
-
end
-
-
1
memoized(:user) { uri.user && Utils.unescape(uri.user) }
-
1
memoized(:password) { uri.password && Utils.unescape(uri.password) }
-
end
-
-
class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
-
1
:parallel_manager, :params, :headers, :builder_class)
-
-
1
options :request => RequestOptions, :ssl => SSLOptions
-
-
1
memoized(:request) { self.class.options_for(:request).new }
-
-
18
memoized(:ssl) { self.class.options_for(:ssl).new }
-
-
18
memoized(:builder_class) { RackBuilder }
-
-
1
def new_builder(block)
-
builder_class.new(&block)
-
end
-
end
-
-
class Env < Options.new(:method, :body, :url, :request, :request_headers,
-
1
:ssl, :parallel_manager, :params, :response, :response_headers, :status)
-
-
1
ContentLength = 'Content-Length'.freeze
-
1
StatusesWithoutBody = Set.new [204, 304]
-
1
SuccessfulStatuses = 200..299
-
-
# A Set of HTTP verbs that typically send a body. If no body is set for
-
# these requests, the Content-Length header is set to 0.
-
1
MethodsWithBodies = Set.new [:post, :put, :patch, :options]
-
-
1
options :request => RequestOptions,
-
:request_headers => Utils::Headers, :response_headers => Utils::Headers
-
-
1
extend Forwardable
-
-
1
def_delegators :request, :params_encoder
-
-
# Public
-
1
def [](key)
-
165
if in_member_set?(key)
-
165
super(key)
-
else
-
custom_members[key]
-
end
-
end
-
-
# Public
-
1
def []=(key, value)
-
if in_member_set?(key)
-
super(key, value)
-
else
-
custom_members[key] = value
-
end
-
end
-
-
# Public
-
1
def success?
-
SuccessfulStatuses.include?(status)
-
end
-
-
# Public
-
1
def needs_body?
-
15
!body && MethodsWithBodies.include?(method)
-
end
-
-
# Public
-
1
def clear_body
-
request_headers[ContentLength] = '0'
-
self.body = ''
-
end
-
-
# Public
-
1
def parse_body?
-
!StatusesWithoutBody.include?(status)
-
end
-
-
# Public
-
1
def parallel?
-
15
!!parallel_manager
-
end
-
-
1
def inspect
-
attrs = [nil]
-
members.each do |mem|
-
if value = send(mem)
-
attrs << "@#{mem}=#{value.inspect}"
-
end
-
end
-
if !custom_members.empty?
-
attrs << "@custom=#{custom_members.inspect}"
-
end
-
%(#<#{self.class}#{attrs.join(" ")}>)
-
end
-
-
# Internal
-
1
def custom_members
-
@custom_members ||= {}
-
end
-
-
# Internal
-
1
if members.first.is_a?(Symbol)
-
1
def in_member_set?(key)
-
165
self.class.member_set.include?(key.to_sym)
-
end
-
else
-
def in_member_set?(key)
-
self.class.member_set.include?(key.to_s)
-
end
-
end
-
-
# Internal
-
1
def self.member_set
-
165
@member_set ||= Set.new(members)
-
end
-
end
-
end
-
1
module Faraday
-
1
module NestedParamsEncoder
-
1
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
-
-
1
def self.escape(s)
-
return s.to_s.gsub(ESCAPE_RE) {
-
'%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
-
60
}.tr(' ', '+')
-
end
-
-
1
def self.unescape(s)
-
CGI.unescape(s.to_s)
-
end
-
-
1
def self.encode(params)
-
15
return nil if params == nil
-
-
15
if !params.is_a?(Array)
-
15
if !params.respond_to?(:to_hash)
-
raise TypeError,
-
"Can't convert #{params.class} into Hash."
-
end
-
15
params = params.to_hash
-
15
params = params.map do |key, value|
-
30
key = key.to_s if key.kind_of?(Symbol)
-
30
[key, value]
-
end
-
# Useful default for OAuth and caching.
-
# Only to be used for non-Array inputs. Arrays should preserve order.
-
15
params.sort!
-
end
-
-
# Helper lambda
-
15
to_query = lambda do |parent, value|
-
30
if value.is_a?(Hash)
-
value = value.map do |key, val|
-
key = escape(key)
-
[key, val]
-
end
-
value.sort!
-
buffer = ""
-
value.each do |key, val|
-
new_parent = "#{parent}%5B#{key}%5D"
-
buffer << "#{to_query.call(new_parent, val)}&"
-
end
-
return buffer.chop
-
elsif value.is_a?(Array)
-
buffer = ""
-
value.each_with_index do |val, i|
-
new_parent = "#{parent}%5B%5D"
-
buffer << "#{to_query.call(new_parent, val)}&"
-
end
-
return buffer.chop
-
else
-
30
encoded_value = escape(value)
-
30
return "#{parent}=#{encoded_value}"
-
end
-
end
-
-
# The params have form [['key1', 'value1'], ['key2', 'value2']].
-
15
buffer = ''
-
15
params.each do |parent, value|
-
30
encoded_parent = escape(parent)
-
30
buffer << "#{to_query.call(encoded_parent, value)}&"
-
end
-
15
return buffer.chop
-
end
-
-
1
def self.decode(query)
-
return nil if query == nil
-
# Recursive helper lambda
-
dehash = lambda do |hash|
-
hash.each do |(key, value)|
-
if value.kind_of?(Hash)
-
hash[key] = dehash.call(value)
-
end
-
end
-
# Numeric keys implies an array
-
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
-
hash.sort.inject([]) do |accu, (_, value)|
-
accu << value; accu
-
end
-
else
-
hash
-
end
-
end
-
-
empty_accumulator = {}
-
return ((query.split('&').map do |pair|
-
pair.split('=', 2) if pair && !pair.empty?
-
end).compact.inject(empty_accumulator.dup) do |accu, (key, value)|
-
key = unescape(key)
-
if value.kind_of?(String)
-
value = unescape(value.gsub(/\+/, ' '))
-
end
-
-
array_notation = !!(key =~ /\[\]$/)
-
subkeys = key.split(/[\[\]]+/)
-
current_hash = accu
-
for i in 0...(subkeys.size - 1)
-
subkey = subkeys[i]
-
current_hash[subkey] = {} unless current_hash[subkey]
-
current_hash = current_hash[subkey]
-
end
-
if array_notation
-
current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
-
current_hash[subkeys.last] << value
-
else
-
current_hash[subkeys.last] = value
-
end
-
accu
-
end).inject(empty_accumulator.dup) do |accu, (key, value)|
-
accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value
-
accu
-
end
-
end
-
end
-
-
1
module FlatParamsEncoder
-
1
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
-
-
1
def self.escape(s)
-
return s.to_s.gsub(ESCAPE_RE) {
-
'%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
-
}.tr(' ', '+')
-
end
-
-
1
def self.unescape(s)
-
CGI.unescape(s.to_s)
-
end
-
-
1
def self.encode(params)
-
return nil if params == nil
-
-
if !params.is_a?(Array)
-
if !params.respond_to?(:to_hash)
-
raise TypeError,
-
"Can't convert #{params.class} into Hash."
-
end
-
params = params.to_hash
-
params = params.map do |key, value|
-
key = key.to_s if key.kind_of?(Symbol)
-
[key, value]
-
end
-
# Useful default for OAuth and caching.
-
# Only to be used for non-Array inputs. Arrays should preserve order.
-
params.sort!
-
end
-
-
# The params have form [['key1', 'value1'], ['key2', 'value2']].
-
buffer = ''
-
params.each do |key, value|
-
encoded_key = escape(key)
-
value = value.to_s if value == true || value == false
-
if value == nil
-
buffer << "#{encoded_key}&"
-
elsif value.kind_of?(Array)
-
value.each do |sub_value|
-
encoded_value = escape(sub_value)
-
buffer << "#{encoded_key}=#{encoded_value}&"
-
end
-
else
-
encoded_value = escape(value)
-
buffer << "#{encoded_key}=#{encoded_value}&"
-
end
-
end
-
return buffer.chop
-
end
-
-
1
def self.decode(query)
-
empty_accumulator = {}
-
return nil if query == nil
-
split_query = (query.split('&').map do |pair|
-
pair.split('=', 2) if pair && !pair.empty?
-
end).compact
-
return split_query.inject(empty_accumulator.dup) do |accu, pair|
-
pair[0] = unescape(pair[0])
-
pair[1] = true if pair[1].nil?
-
if pair[1].respond_to?(:to_str)
-
pair[1] = unescape(pair[1].to_str.gsub(/\+/, " "))
-
end
-
if accu[pair[0]].kind_of?(Array)
-
accu[pair[0]] << pair[1]
-
elsif accu[pair[0]]
-
accu[pair[0]] = [accu[pair[0]], pair[1]]
-
else
-
accu[pair[0]] = pair[1]
-
end
-
accu
-
end
-
end
-
end
-
end
-
1
module Faraday
-
# A Builder that processes requests into responses by passing through an inner
-
# middleware stack (heavily inspired by Rack).
-
#
-
# Faraday::Connection.new(:url => 'http://sushi.com') do |builder|
-
# builder.request :url_encoded # Faraday::Request::UrlEncoded
-
# builder.adapter :net_http # Faraday::Adapter::NetHttp
-
# end
-
1
class RackBuilder
-
1
attr_accessor :handlers
-
-
# Error raised when trying to modify the stack after calling `lock!`
-
1
class StackLocked < RuntimeError; end
-
-
# borrowed from ActiveSupport::Dependencies::Reference &
-
# ActionDispatch::MiddlewareStack::Middleware
-
1
class Handler
-
1
@@constants_mutex = Mutex.new
-
1
@@constants = Hash.new { |h, k|
-
value = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k)
-
@@constants_mutex.synchronize { h[k] = value }
-
}
-
-
1
attr_reader :name
-
-
1
def initialize(klass, *args, &block)
-
90
@name = klass.to_s
-
90
if klass.respond_to?(:name)
-
180
@@constants_mutex.synchronize { @@constants[@name] = klass }
-
end
-
90
@args, @block = args, block
-
end
-
-
76
def klass() @@constants[@name] end
-
1
def inspect() @name end
-
-
1
def ==(other)
-
if other.is_a? Handler
-
self.name == other.name
-
elsif other.respond_to? :name
-
klass == other
-
else
-
@name == other.to_s
-
end
-
end
-
-
1
def build(app)
-
75
klass.new(app, *@args, &@block)
-
end
-
end
-
-
1
def initialize(handlers = [])
-
18
@handlers = handlers
-
18
if block_given?
-
18
build(&Proc.new)
-
elsif @handlers.empty?
-
# default stack, if nothing else is configured
-
self.request :url_encoded
-
self.adapter Faraday.default_adapter
-
end
-
end
-
-
1
def build(options = {})
-
18
raise_if_locked
-
18
@handlers.clear unless options[:keep]
-
18
yield(self) if block_given?
-
end
-
-
1
def [](idx)
-
@handlers[idx]
-
end
-
-
# Locks the middleware stack to ensure no further modifications are possible.
-
1
def lock!
-
15
@handlers.freeze
-
end
-
-
1
def locked?
-
108
@handlers.frozen?
-
end
-
-
1
def use(klass, *args, &block)
-
90
if klass.is_a? Symbol
-
use_symbol(Faraday::Middleware, klass, *args, &block)
-
else
-
90
raise_if_locked
-
90
@handlers << self.class::Handler.new(klass, *args, &block)
-
end
-
end
-
-
1
def request(key, *args, &block)
-
18
use_symbol(Faraday::Request, key, *args, &block)
-
end
-
-
1
def response(key, *args, &block)
-
54
use_symbol(Faraday::Response, key, *args, &block)
-
end
-
-
1
def adapter(key, *args, &block)
-
18
use_symbol(Faraday::Adapter, key, *args, &block)
-
end
-
-
## methods to push onto the various positions in the stack:
-
-
1
def insert(index, *args, &block)
-
raise_if_locked
-
index = assert_index(index)
-
handler = self.class::Handler.new(*args, &block)
-
@handlers.insert(index, handler)
-
end
-
-
1
alias_method :insert_before, :insert
-
-
1
def insert_after(index, *args, &block)
-
index = assert_index(index)
-
insert(index + 1, *args, &block)
-
end
-
-
1
def swap(index, *args, &block)
-
raise_if_locked
-
index = assert_index(index)
-
@handlers.delete_at(index)
-
insert(index, *args, &block)
-
end
-
-
1
def delete(handler)
-
raise_if_locked
-
@handlers.delete(handler)
-
end
-
-
# Processes a Request into a Response by passing it through this Builder's
-
# middleware stack.
-
#
-
# connection - Faraday::Connection
-
# request - Faraday::Request
-
#
-
# Returns a Faraday::Response.
-
1
def build_response(connection, request)
-
15
app.call(build_env(connection, request))
-
end
-
-
# The "rack app" wrapped in middleware. All requests are sent here.
-
#
-
# The builder is responsible for creating the app object. After this,
-
# the builder gets locked to ensure no further modifications are made
-
# to the middleware stack.
-
#
-
# Returns an object that responds to `call` and returns a Response.
-
1
def app
-
@app ||= begin
-
15
lock!
-
15
to_app(lambda { |env|
-
15
response = Response.new
-
15
response.finish(env) unless env.parallel?
-
15
env.response = response
-
})
-
15
end
-
end
-
-
1
def to_app(inner_app)
-
# last added handler is the deepest and thus closest to the inner app
-
90
@handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
-
end
-
-
1
def ==(other)
-
other.is_a?(self.class) && @handlers == other.handlers
-
end
-
-
1
def dup
-
self.class.new(@handlers.dup)
-
end
-
-
# ENV Keys
-
# :method - a symbolized request method (:get, :post)
-
# :body - the request body that will eventually be converted to a string.
-
# :url - URI instance for the current request.
-
# :status - HTTP response status code
-
# :request_headers - hash of HTTP Headers to be sent to the server
-
# :response_headers - Hash of HTTP headers from the server
-
# :parallel_manager - sent if the connection is in parallel mode
-
# :request - Hash of options for configuring the request.
-
# :timeout - open/read timeout Integer in seconds
-
# :open_timeout - read timeout Integer in seconds
-
# :proxy - Hash of proxy options
-
# :uri - Proxy Server URI
-
# :user - Proxy server username
-
# :password - Proxy server password
-
# :ssl - Hash of options for configuring SSL requests.
-
1
def build_env(connection, request)
-
15
Env.new(request.method, request.body,
-
connection.build_exclusive_url(request.path, request.params),
-
request.options, request.headers, connection.ssl,
-
connection.parallel_manager)
-
end
-
-
1
private
-
-
1
def raise_if_locked
-
108
raise StackLocked, "can't modify middleware stack after making a request" if locked?
-
end
-
-
1
def use_symbol(mod, key, *args, &block)
-
90
use(mod.lookup_middleware(key), *args, &block)
-
end
-
-
1
def assert_index(index)
-
idx = index.is_a?(Integer) ? index : @handlers.index(index)
-
raise "No such handler: #{index.inspect}" unless idx
-
idx
-
end
-
end
-
end
-
1
module Faraday
-
# Used to setup urls, params, headers, and the request body in a sane manner.
-
#
-
# @connection.post do |req|
-
# req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1'
-
# req.headers['b'] = '2' # Header
-
# req.params['c'] = '3' # GET Param
-
# req['b'] = '2' # also Header
-
# req.body = 'abc'
-
# end
-
#
-
1
class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
-
1
extend MiddlewareRegistry
-
-
1
register_middleware File.expand_path('../request', __FILE__),
-
:url_encoded => [:UrlEncoded, 'url_encoded'],
-
:multipart => [:Multipart, 'multipart'],
-
:retry => [:Retry, 'retry'],
-
:authorization => [:Authorization, 'authorization'],
-
:basic_auth => [:BasicAuthentication, 'basic_authentication'],
-
:token_auth => [:TokenAuthentication, 'token_authentication'],
-
:instrumentation => [:Instrumentation, 'instrumentation']
-
-
1
def self.create(request_method)
-
15
new(request_method).tap do |request|
-
15
yield(request) if block_given?
-
end
-
end
-
-
# Public: Replace params, preserving the existing hash type
-
1
def params=(hash)
-
15
if params
-
params.replace hash
-
else
-
15
super
-
end
-
end
-
-
# Public: Replace request headers, preserving the existing hash type
-
1
def headers=(hash)
-
15
if headers
-
headers.replace hash
-
else
-
15
super
-
end
-
end
-
-
1
def url(path, params = nil)
-
15
if path.respond_to? :query
-
15
if query = path.query
-
path = path.dup
-
path.query = nil
-
end
-
else
-
path, query = path.split('?', 2)
-
end
-
15
self.path = path
-
15
self.params.merge_query query, options.params_encoder
-
15
self.params.update(params) if params
-
end
-
-
1
def [](key)
-
headers[key]
-
end
-
-
1
def []=(key, value)
-
headers[key] = value
-
end
-
-
# ENV Keys
-
# :method - a symbolized request method (:get, :post)
-
# :body - the request body that will eventually be converted to a string.
-
# :url - URI instance for the current request.
-
# :status - HTTP response status code
-
# :request_headers - hash of HTTP Headers to be sent to the server
-
# :response_headers - Hash of HTTP headers from the server
-
# :parallel_manager - sent if the connection is in parallel mode
-
# :request - Hash of options for configuring the request.
-
# :timeout - open/read timeout Integer in seconds
-
# :open_timeout - read timeout Integer in seconds
-
# :proxy - Hash of proxy options
-
# :uri - Proxy Server URI
-
# :user - Proxy server username
-
# :password - Proxy server password
-
# :ssl - Hash of options for configuring SSL requests.
-
1
def to_env(connection)
-
Env.new(method, body, connection.build_exclusive_url(path, params),
-
options, headers, connection.ssl, connection.parallel_manager)
-
end
-
end
-
end
-
-
1
module Faraday
-
1
class Request::UrlEncoded < Faraday::Middleware
-
1
CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE
-
-
1
class << self
-
1
attr_accessor :mime_type
-
end
-
1
self.mime_type = 'application/x-www-form-urlencoded'.freeze
-
-
1
def call(env)
-
15
match_content_type(env) do |data|
-
params = Faraday::Utils::ParamsHash[data]
-
env.body = params.to_query(env.params_encoder)
-
end
-
15
@app.call env
-
end
-
-
1
def match_content_type(env)
-
15
if process_request?(env)
-
env.request_headers[CONTENT_TYPE] ||= self.class.mime_type
-
yield(env.body) unless env.body.respond_to?(:to_str)
-
end
-
end
-
-
1
def process_request?(env)
-
15
type = request_type(env)
-
15
env.body and (type.empty? or type == self.class.mime_type)
-
end
-
-
1
def request_type(env)
-
15
type = env.request_headers[CONTENT_TYPE].to_s
-
15
type = type.split(';', 2).first if type.index(';')
-
15
type
-
end
-
end
-
end
-
1
require 'forwardable'
-
-
1
module Faraday
-
1
class Response
-
# Used for simple response middleware.
-
1
class Middleware < Faraday::Middleware
-
1
def call(env)
-
45
@app.call(env).on_complete do |environment|
-
45
on_complete(environment)
-
end
-
end
-
-
# Override this to modify the environment after the response has finished.
-
# Calls the `parse` method if defined
-
1
def on_complete(env)
-
env.body = parse(env.body) if respond_to?(:parse) && env.parse_body?
-
end
-
end
-
-
1
extend Forwardable
-
1
extend MiddlewareRegistry
-
-
1
register_middleware File.expand_path('../response', __FILE__),
-
:raise_error => [:RaiseError, 'raise_error'],
-
:logger => [:Logger, 'logger']
-
-
1
def initialize(env = nil)
-
15
@env = Env.from(env) if env
-
15
@on_complete_callbacks = []
-
end
-
-
1
attr_reader :env
-
-
1
def_delegators :env, :to_hash
-
-
1
def status
-
finished? ? env.status : nil
-
end
-
-
1
def headers
-
finished? ? env.response_headers : {}
-
end
-
1
def_delegator :headers, :[]
-
-
1
def body
-
finished? ? env.body : nil
-
end
-
-
1
def finished?
-
60
!!env
-
end
-
-
1
def on_complete
-
45
if not finished?
-
@on_complete_callbacks << Proc.new
-
else
-
45
yield(env)
-
end
-
35
return self
-
end
-
-
1
def finish(env)
-
15
raise "response already finished" if finished?
-
15
@env = Env.from(env)
-
15
@on_complete_callbacks.each { |callback| callback.call(env) }
-
15
return self
-
end
-
-
1
def success?
-
finished? && env.success?
-
end
-
-
# because @on_complete_callbacks cannot be marshalled
-
1
def marshal_dump
-
!finished? ? nil : {
-
:status => @env.status, :body => @env.body,
-
:response_headers => @env.response_headers
-
}
-
end
-
-
1
def marshal_load(env)
-
@env = Env.from(env)
-
end
-
-
# Expand the env with more properties, without overriding existing ones.
-
# Useful for applying request params after restoring a marshalled Response.
-
1
def apply_request(request_env)
-
raise "response didn't finish yet" unless finished?
-
@env = Env.from(request_env).update(@env)
-
return self
-
end
-
end
-
end
-
1
require 'forwardable'
-
-
1
module Faraday
-
1
class Response::Logger < Response::Middleware
-
1
extend Forwardable
-
-
1
def initialize(app, logger = nil)
-
15
super(app)
-
15
@logger = logger || begin
-
15
require 'logger'
-
15
::Logger.new(STDOUT)
-
end
-
end
-
-
1
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
-
-
1
def call(env)
-
15
info "#{env.method} #{env.url.to_s}"
-
30
debug('request') { dump_headers env.request_headers }
-
15
super
-
end
-
-
1
def on_complete(env)
-
30
info('Status') { env.status.to_s }
-
30
debug('response') { dump_headers env.response_headers }
-
end
-
-
1
private
-
-
1
def dump_headers(headers)
-
75
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
-
end
-
end
-
end
-
1
begin
-
1
require 'composite_io'
-
1
require 'parts'
-
1
require 'stringio'
-
rescue LoadError
-
$stderr.puts "Install the multipart-post gem."
-
raise
-
end
-
-
1
module Faraday
-
# Similar but not compatible with ::CompositeReadIO provided by multipart-post.
-
1
class CompositeReadIO
-
1
def initialize(*parts)
-
@parts = parts.flatten
-
@ios = @parts.map { |part| part.to_io }
-
@index = 0
-
end
-
-
1
def length
-
@parts.inject(0) { |sum, part| sum + part.length }
-
end
-
-
1
def rewind
-
@ios.each { |io| io.rewind }
-
@index = 0
-
end
-
-
# Read from IOs in order until `length` bytes have been received.
-
1
def read(length = nil, outbuf = nil)
-
got_result = false
-
outbuf = outbuf ? outbuf.replace("") : ""
-
-
while io = current_io
-
if result = io.read(length)
-
got_result ||= !result.nil?
-
result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
-
outbuf << result
-
length -= result.length if length
-
break if length == 0
-
end
-
advance_io
-
end
-
(!got_result && length) ? nil : outbuf
-
end
-
-
1
def close
-
@ios.each { |io| io.close }
-
end
-
-
1
def ensure_open_and_readable
-
# Rubinius compatibility
-
end
-
-
1
private
-
-
1
def current_io
-
@ios[@index]
-
end
-
-
1
def advance_io
-
@index += 1
-
end
-
end
-
-
1
UploadIO = ::UploadIO
-
1
Parts = ::Parts
-
end
-
1
require 'thread'
-
1
Faraday.require_libs 'parameters'
-
-
1
module Faraday
-
1
module Utils
-
1
extend self
-
-
# Adapted from Rack::Utils::HeaderHash
-
1
class Headers < ::Hash
-
1
def self.from(value)
-
30
new(value)
-
end
-
-
1
def initialize(hash = nil)
-
62
super()
-
62
@names = {}
-
62
self.update(hash || {})
-
end
-
-
# need to synchronize concurrent writes to the shared KeyMap
-
1
keymap_mutex = Mutex.new
-
-
# symbol -> string mapper + cache
-
1
KeyMap = Hash.new do |map, key|
-
7
value = if key.respond_to?(:to_str)
-
5
key
-
else
-
key.to_s.split('_'). # :user_agent => %w(user agent)
-
3
each { |w| w.capitalize! }. # => %w(User Agent)
-
2
join('-') # => "User-Agent"
-
end
-
14
keymap_mutex.synchronize { map[key] = value }
-
end
-
1
KeyMap[:etag] = "ETag"
-
-
1
def [](k)
-
32
k = KeyMap[k]
-
32
super(k) || super(@names[k.downcase])
-
end
-
-
1
def []=(k, v)
-
109
k = KeyMap[k]
-
109
k = (@names[k.downcase] ||= k)
-
# join multiple values with a comma
-
109
v = v.to_ary.join(', ') if v.respond_to? :to_ary
-
109
super(k, v)
-
end
-
-
1
def fetch(k, *args, &block)
-
k = KeyMap[k]
-
key = @names.fetch(k.downcase, k)
-
super(key, *args, &block)
-
end
-
-
1
def delete(k)
-
k = KeyMap[k]
-
if k = @names[k.downcase]
-
@names.delete k.downcase
-
super(k)
-
end
-
end
-
-
1
def include?(k)
-
@names.include? k.downcase
-
end
-
-
1
alias_method :has_key?, :include?
-
1
alias_method :member?, :include?
-
1
alias_method :key?, :include?
-
-
1
def merge!(other)
-
173
other.each { |k, v| self[k] = v }
-
94
self
-
end
-
1
alias_method :update, :merge!
-
-
1
def merge(other)
-
hash = dup
-
hash.merge! other
-
end
-
-
1
def replace(other)
-
clear
-
self.update other
-
self
-
end
-
-
1
def to_hash() ::Hash.new.update(self) end
-
-
1
def parse(header_string)
-
return unless header_string && !header_string.empty?
-
header_string.split(/\r\n/).
-
tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
-
map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
-
each { |key, value|
-
# join multiple values with a comma
-
if self[key]
-
self[key] << ', ' << value
-
else
-
self[key] = value
-
end
-
}
-
end
-
end
-
-
# hash with stringified keys
-
1
class ParamsHash < Hash
-
1
def [](key)
-
super(convert_key(key))
-
end
-
-
1
def []=(key, value)
-
30
super(convert_key(key), value)
-
end
-
-
1
def delete(key)
-
super(convert_key(key))
-
end
-
-
1
def include?(key)
-
super(convert_key(key))
-
end
-
-
1
alias_method :has_key?, :include?
-
1
alias_method :member?, :include?
-
1
alias_method :key?, :include?
-
-
1
def update(params)
-
15
params.each do |key, value|
-
30
self[key] = value
-
end
-
15
self
-
end
-
1
alias_method :merge!, :update
-
-
1
def merge(params)
-
dup.update(params)
-
end
-
-
1
def replace(other)
-
clear
-
update(other)
-
end
-
-
1
def merge_query(query, encoder = nil)
-
32
if query && !query.empty?
-
update((encoder || Utils.default_params_encoder).decode(query))
-
end
-
32
self
-
end
-
-
1
def to_query(encoder = nil)
-
15
(encoder || Utils.default_params_encoder).encode(self)
-
end
-
-
1
private
-
-
1
def convert_key(key)
-
30
key.to_s
-
end
-
end
-
-
1
def build_query(params)
-
FlatParamsEncoder.encode(params)
-
end
-
-
1
def build_nested_query(params)
-
NestedParamsEncoder.encode(params)
-
end
-
-
1
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
-
-
1
def escape(s)
-
s.to_s.gsub(ESCAPE_RE) {|match|
-
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
-
}.tr(' ', '+')
-
end
-
-
1
def unescape(s) CGI.unescape s.to_s end
-
-
1
DEFAULT_SEP = /[&;] */n
-
-
# Adapted from Rack
-
1
def parse_query(query)
-
FlatParamsEncoder.decode(query)
-
end
-
-
1
def parse_nested_query(query)
-
NestedParamsEncoder.decode(query)
-
end
-
-
1
def default_params_encoder
-
15
@default_params_encoder ||= NestedParamsEncoder
-
end
-
-
1
class << self
-
1
attr_writer :default_params_encoder
-
end
-
-
# Stolen from Rack
-
1
def normalize_params(params, name, v = nil)
-
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
-
k = $1 || ''
-
after = $' || ''
-
-
return if k.empty?
-
-
if after == ""
-
if params[k]
-
params[k] = Array[params[k]] unless params[k].kind_of?(Array)
-
params[k] << v
-
else
-
params[k] = v
-
end
-
elsif after == "[]"
-
params[k] ||= []
-
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
-
params[k] << v
-
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
-
child_key = $1
-
params[k] ||= []
-
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
-
if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
-
normalize_params(params[k].last, child_key, v)
-
else
-
params[k] << normalize_params({}, child_key, v)
-
end
-
else
-
params[k] ||= {}
-
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
-
params[k] = normalize_params(params[k], after, v)
-
end
-
-
return params
-
end
-
-
# Normalize URI() behavior across Ruby versions
-
#
-
# url - A String or URI.
-
#
-
# Returns a parsed URI.
-
1
def URI(url)
-
17
if url.respond_to?(:host)
-
url
-
17
elsif url.respond_to?(:to_str)
-
17
default_uri_parser.call(url)
-
else
-
raise ArgumentError, "bad argument (expected URI object or URI string)"
-
end
-
end
-
-
1
def default_uri_parser
-
@default_uri_parser ||= begin
-
1
require 'uri'
-
1
Kernel.method(:URI)
-
17
end
-
end
-
-
1
def default_uri_parser=(parser)
-
@default_uri_parser = if parser.respond_to?(:call) || parser.nil?
-
parser
-
else
-
parser.method(:parse)
-
end
-
end
-
-
# Receives a String or URI and returns just the path with the query string sorted.
-
1
def normalize_path(url)
-
url = URI(url)
-
(url.path.start_with?('/') ? url.path : '/' + url.path) +
-
(url.query ? "?#{sort_query_params(url.query)}" : "")
-
end
-
-
# Recursive hash update
-
1
def deep_merge!(target, hash)
-
hash.each do |key, value|
-
if Hash === value and Hash === target[key]
-
target[key] = deep_merge(target[key], value)
-
else
-
target[key] = value
-
end
-
end
-
target
-
end
-
-
# Recursive hash merge
-
1
def deep_merge(source, hash)
-
deep_merge!(source.dup, hash)
-
end
-
-
1
protected
-
-
1
def sort_query_params(query)
-
query.split('&').sort.join('&')
-
end
-
end
-
end
-
1
require 'http/parser'
-
-
1
require 'http/errors'
-
1
require 'http/chainable'
-
1
require 'http/client'
-
1
require 'http/options'
-
1
require 'http/request'
-
1
require 'http/request/writer'
-
1
require 'http/response'
-
1
require 'http/response/body'
-
1
require 'http/response/parser'
-
1
require 'http/backports'
-
-
# HTTP should be easy
-
1
module HTTP
-
1
extend Chainable
-
-
1
class << self
-
# HTTP[:accept => 'text/html'].get(...)
-
1
alias_method :[], :with_headers
-
end
-
end
-
-
1
Http = HTTP unless defined?(Http)
-
1
module HTTP
-
# Authorization header value builders
-
1
module AuthorizationHeader
-
1
class << self
-
# Associate type with given builder.
-
# @param [#to_sym] type
-
# @param [Class] klass
-
# @return [void]
-
1
def register(type, klass)
-
2
builders[type.to_sym] = klass
-
end
-
-
# Builds Authorization header value with associated builder.
-
# @param [#to_sym] type
-
# @param [Object] opts
-
# @return [String]
-
1
def build(type, opts)
-
klass = builders[type.to_sym]
-
-
fail Error, "Unknown authorization type #{type}" unless klass
-
-
klass.new opts
-
end
-
-
1
private
-
-
# :nodoc:
-
1
def builders
-
2
@builders ||= {}
-
end
-
end
-
end
-
end
-
-
# built-in builders
-
1
require 'http/authorization_header/basic_auth'
-
1
require 'http/authorization_header/bearer_token'
-
1
require 'base64'
-
-
1
module HTTP
-
1
module AuthorizationHeader
-
# Basic authorization header builder
-
# @see http://tools.ietf.org/html/rfc2617
-
1
class BasicAuth
-
# @param [#fetch] opts
-
# @option opts [#to_s] :user
-
# @option opts [#to_s] :pass
-
1
def initialize(opts)
-
@user = opts.fetch :user
-
@pass = opts.fetch :pass
-
end
-
-
# :nodoc:
-
1
def to_s
-
'Basic ' << Base64.strict_encode64("#{@user}:#{@pass}")
-
end
-
end
-
-
1
register :basic, BasicAuth
-
end
-
end
-
1
require 'base64'
-
-
1
module HTTP
-
1
module AuthorizationHeader
-
# OAuth2 Bearer token authorization header builder
-
# @see http://tools.ietf.org/html/rfc6750
-
#
-
# @deprecated Will be remove in v0.7.0
-
1
class BearerToken
-
# @param [#fetch] opts
-
# @option opts [#to_s] :token
-
# @option opts [Boolean] :encode (false) deprecated
-
1
def initialize(opts)
-
warn "#{Kernel.caller.first}: [DEPRECATION] BearerToken deprecated."
-
-
@token = opts.fetch :token
-
@token = Base64.strict_encode64 @token if opts.fetch(:encode, false)
-
end
-
-
# :nodoc:
-
1
def to_s
-
"Bearer #{@token}"
-
end
-
end
-
-
1
register :bearer, BearerToken
-
end
-
end
-
1
require 'http/backports/uri' if RUBY_VERSION < '1.9.3'
-
1
require 'http/backports/base64' if RUBY_VERSION < '1.9.0'
-
1
require 'http/authorization_header'
-
-
1
module HTTP
-
1
module Chainable
-
# Request a get sans response body
-
1
def head(uri, options = {})
-
request :head, uri, options
-
end
-
-
# Get a resource
-
1
def get(uri, options = {})
-
request :get, uri, options
-
end
-
-
# Post to a resource
-
1
def post(uri, options = {})
-
request :post, uri, options
-
end
-
-
# Put to a resource
-
1
def put(uri, options = {})
-
request :put, uri, options
-
end
-
-
# Delete a resource
-
1
def delete(uri, options = {})
-
request :delete, uri, options
-
end
-
-
# Echo the request back to the client
-
1
def trace(uri, options = {})
-
request :trace, uri, options
-
end
-
-
# Return the methods supported on the given URI
-
1
def options(uri, options = {})
-
request :options, uri, options
-
end
-
-
# Convert to a transparent TCP/IP tunnel
-
1
def connect(uri, options = {})
-
request :connect, uri, options
-
end
-
-
# Apply partial modifications to a resource
-
1
def patch(uri, options = {})
-
request :patch, uri, options
-
end
-
-
# Make an HTTP request with the given verb
-
1
def request(verb, uri, options = {})
-
branch(options).request verb, uri
-
end
-
-
# Make a request through an HTTP proxy
-
1
def via(*proxy)
-
proxy_hash = {}
-
proxy_hash[:proxy_address] = proxy[0] if proxy[0].is_a?(String)
-
proxy_hash[:proxy_port] = proxy[1] if proxy[1].is_a?(Integer)
-
proxy_hash[:proxy_username] = proxy[2] if proxy[2].is_a?(String)
-
proxy_hash[:proxy_password] = proxy[3] if proxy[3].is_a?(String)
-
-
if [2, 4].include?(proxy_hash.keys.size)
-
branch default_options.with_proxy(proxy_hash)
-
else
-
fail(RequestError, "invalid HTTP proxy: #{proxy_hash}")
-
end
-
end
-
1
alias_method :through, :via
-
-
# Alias for with_response(:object)
-
1
def stream
-
with_response(:object)
-
end
-
-
# Make client follow redirects.
-
# @param opts (see Redirector#initialize)
-
# @return [HTTP::Client]
-
1
def follow(opts = true)
-
branch default_options.with_follow opts
-
end
-
-
# (see #follow)
-
# @deprecated
-
1
alias_method :with_follow, :follow
-
-
# Make a request with the given headers
-
1
def with_headers(headers)
-
branch default_options.with_headers(headers)
-
end
-
1
alias_method :with, :with_headers
-
-
# Accept the given MIME type(s)
-
1
def accept(type)
-
with :accept => MimeType.normalize(type)
-
end
-
-
# Make a request with the given Authorization header
-
1
def auth(*args)
-
value = case args.count
-
when 1 then args.first
-
when 2 then AuthorizationHeader.build(*args)
-
else fail ArgumentError, "wrong number of arguments (#{args.count} for 1..2)"
-
end
-
-
with :authorization => value.to_s
-
end
-
-
1
def default_options
-
@default_options ||= HTTP::Options.new
-
end
-
-
1
def default_options=(opts)
-
@default_options = HTTP::Options.new(opts)
-
end
-
-
1
def default_headers
-
default_options.headers
-
end
-
-
1
def default_headers=(headers)
-
@default_options = default_options.dup do |opts|
-
opts.headers = headers
-
end
-
end
-
-
1
def default_callbacks
-
default_options.callbacks
-
end
-
-
1
def default_callbacks=(callbacks)
-
@default_options = default_options.dup do |opts|
-
opts.callbacks = callbacks
-
end
-
end
-
-
1
private
-
-
1
def branch(options)
-
HTTP::Client.new(options)
-
end
-
end
-
end
-
1
require 'cgi'
-
1
require 'uri'
-
1
require 'http/options'
-
1
require 'http/redirector'
-
-
1
module HTTP
-
# Clients make requests and receive responses
-
1
class Client
-
1
include Chainable
-
-
# Input buffer size
-
1
BUFFER_SIZE = 16_384
-
-
1
attr_reader :default_options
-
-
1
def initialize(default_options = {})
-
@default_options = HTTP::Options.new(default_options)
-
@parser = HTTP::Response::Parser.new
-
@socket = nil
-
end
-
-
# Make an HTTP request
-
1
def request(verb, uri, opts = {})
-
opts = @default_options.merge(opts)
-
uri = make_request_uri(uri, opts)
-
headers = opts.headers
-
proxy = opts.proxy
-
body = make_request_body(opts, headers)
-
-
req = HTTP::Request.new(verb, uri, headers, proxy, body)
-
res = perform req, opts
-
-
if opts.follow
-
res = Redirector.new(opts.follow).perform req, res do |request|
-
perform request, opts
-
end
-
end
-
-
res
-
end
-
-
# Perform a single (no follow) HTTP request
-
1
def perform(req, options)
-
# finish previous response if client was re-used
-
# TODO: this is pretty wrong, as socket shoud be part of response
-
# connection, so that re-use of client will not break multiple
-
# chunked responses
-
finish_response
-
-
uri = req.uri
-
-
# TODO: keep-alive support
-
@socket = options[:socket_class].open(req.socket_host, req.socket_port)
-
@socket = start_tls(@socket, options) if uri.is_a?(URI::HTTPS)
-
-
req.stream @socket
-
-
begin
-
read_more BUFFER_SIZE until @parser.headers
-
rescue IOError, Errno::ECONNRESET, Errno::EPIPE => ex
-
raise IOError, "problem making HTTP request: #{ex}"
-
end
-
-
body = Response::Body.new(self)
-
res = Response.new(@parser.status_code, @parser.http_version, @parser.headers, body, uri)
-
-
finish_response if :head == req.verb
-
-
res
-
end
-
-
# Read a chunk of the body
-
1
def readpartial(size = BUFFER_SIZE)
-
return unless @socket
-
-
read_more size
-
chunk = @parser.chunk
-
-
finish_response if @parser.finished?
-
-
chunk.to_s
-
end
-
-
1
private
-
-
# Initialize TLS connection
-
1
def start_tls(socket, options)
-
# TODO: abstract away SSLContexts so we can use other TLS libraries
-
context = options[:ssl_context] || OpenSSL::SSL::SSLContext.new
-
socket = options[:ssl_socket_class].new(socket, context)
-
-
socket.connect
-
socket
-
end
-
-
# Merges query params if needed
-
1
def make_request_uri(uri, options)
-
uri = URI uri.to_s unless uri.is_a? URI
-
-
if options.params && !options.params.empty?
-
params = CGI.parse(uri.query.to_s).merge(options.params || {})
-
uri.query = URI.encode_www_form params
-
end
-
-
uri
-
end
-
-
# Create the request body object to send
-
1
def make_request_body(opts, headers)
-
if opts.body
-
opts.body
-
elsif opts.form
-
headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
-
URI.encode_www_form(opts.form)
-
elsif opts.json
-
headers['Content-Type'] ||= 'application/json'
-
MimeType[:json].encode opts.json
-
end
-
end
-
-
# Callback for when we've reached the end of a response
-
1
def finish_response
-
@socket.close if @socket && !@socket.closed?
-
@parser.reset
-
-
@socket = nil
-
end
-
-
# Feeds some more data into parser
-
1
def read_more(size)
-
@parser << @socket.readpartial(size) unless @parser.finished?
-
true
-
rescue EOFError
-
false
-
end
-
end
-
end
-
1
module HTTP
-
1
ContentType = Struct.new(:mime_type, :charset) do
-
1
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}
-
1
CHARSET_RE = /;\s*charset=([^;]+)/i
-
-
1
class << self
-
# Parse string and return ContentType struct
-
1
def parse(str)
-
new mime_type(str), charset(str)
-
end
-
-
1
private
-
-
# :nodoc:
-
1
def mime_type(str)
-
md = str.to_s.match MIME_TYPE_RE
-
md && md[1].to_s.strip.downcase
-
end
-
-
# :nodoc:
-
1
def charset(str)
-
md = str.to_s.match CHARSET_RE
-
md && md[1].to_s.strip.gsub(/^"|"$/, '')
-
end
-
end
-
end
-
end
-
1
module HTTP
-
# Generic error
-
1
class Error < StandardError; end
-
-
# Generic Request error
-
1
class RequestError < Error; end
-
-
# Generic Response error
-
1
class ResponseError < Error; end
-
-
# Request to do something when we're in the wrong state
-
1
class StateError < ResponseError; end
-
end
-
1
require 'forwardable'
-
-
1
require 'http/headers/mixin'
-
-
1
module HTTP
-
1
class Headers
-
1
extend Forwardable
-
1
include Enumerable
-
-
# Matches HTTP header names when in "Canonical-Http-Format"
-
1
CANONICAL_HEADER = /^[A-Z][a-z]*(-[A-Z][a-z]*)*$/
-
-
# :nodoc:
-
1
def initialize
-
@pile = []
-
end
-
-
# Sets header
-
#
-
# @return [void]
-
1
def set(name, value)
-
delete(name)
-
add(name, value)
-
end
-
1
alias_method :[]=, :set
-
-
# Removes header
-
#
-
# @return [void]
-
1
def delete(name)
-
name = canonicalize_header name.to_s
-
@pile.delete_if { |k, _| k == name }
-
end
-
-
# Append header
-
#
-
# @return [void]
-
1
def add(name, value)
-
name = canonicalize_header name.to_s
-
Array(value).each { |v| @pile << [name, v] }
-
end
-
1
alias_method :append, :add
-
-
# Return array of header values if any.
-
#
-
# @return [Array]
-
1
def get(name)
-
name = canonicalize_header name.to_s
-
@pile.select { |k, _| k == name }.map { |_, v| v }
-
end
-
-
# Smart version of {#get}
-
#
-
# @return [NilClass] if header was not set
-
# @return [Object] if header has exactly one value
-
# @return [Array<Object>] if header has more than one value
-
1
def [](name)
-
values = get(name)
-
-
case values.count
-
when 0 then nil
-
when 1 then values.first
-
else values
-
end
-
end
-
-
# Converts headers into a Rack-compatible Hash
-
#
-
# @return [Hash]
-
1
def to_h
-
Hash[keys.map { |k| [k, self[k]] }]
-
end
-
-
# Array of key/value pairs
-
#
-
# @return [Array<[String, String]>]
-
1
def to_a
-
@pile.map { |pair| pair.map(&:dup) }
-
end
-
-
# :nodoc:
-
1
def inspect
-
"#<#{self.class} #{to_h.inspect}>"
-
end
-
-
# List of header names
-
#
-
# @return [Array<String>]
-
1
def keys
-
@pile.map { |k, _| k }.uniq
-
end
-
-
# Compares headers to another Headers or Array of key/value pairs
-
#
-
# @return [Boolean]
-
1
def ==(other)
-
return false unless other.respond_to? :to_a
-
@pile == other.to_a
-
end
-
-
1
def_delegators :@pile, :each, :empty?, :hash
-
-
# :nodoc:
-
1
def initialize_copy(orig)
-
super
-
@pile = to_a
-
end
-
-
# Merge in `other` headers
-
#
-
# @see #merge
-
# @return [void]
-
1
def merge!(other)
-
self.class.coerce(other).to_h.each { |name, values| set name, values }
-
end
-
-
# Returns new Headers instance with `other` headers merged in.
-
#
-
# @see #merge!
-
# @return [Headers]
-
1
def merge(other)
-
dup.tap { |dupped| dupped.merge! other }
-
end
-
-
# Initiates new Headers object from given object.
-
#
-
# @raise [Error] if given object can't be coerced
-
# @param [#to_hash, #to_h, #to_a] object
-
# @return [Headers]
-
1
def self.coerce(object)
-
unless object.is_a? self
-
object = case
-
when object.respond_to?(:to_hash) then object.to_hash
-
when object.respond_to?(:to_h) then object.to_h
-
when object.respond_to?(:to_a) then object.to_a
-
else fail Error, "Can't coerce #{object.inspect} to Headers"
-
end
-
end
-
-
headers = new
-
object.each { |k, v| headers.add k, v }
-
headers
-
end
-
-
1
private
-
-
# Transform to canonical HTTP header capitalization
-
# @param [String] name
-
# @return [String]
-
1
def canonicalize_header(name)
-
name[CANONICAL_HEADER] || name.split(/[\-_]/).map(&:capitalize).join('-')
-
end
-
end
-
end
-
1
require 'forwardable'
-
-
1
module HTTP
-
1
class Headers
-
1
module Mixin
-
1
extend Forwardable
-
1
attr_reader :headers
-
1
def_delegators :headers, :[], :[]=
-
end
-
end
-
end
-
1
module HTTP
-
# MIME type encode/decode adapters
-
1
module MimeType
-
1
class << self
-
# Associate MIME type with adapter
-
#
-
# @example
-
#
-
# module JsonAdapter
-
# class << self
-
# def encode(obj)
-
# # encode logic here
-
# end
-
#
-
# def decode(str)
-
# # decode logic here
-
# end
-
# end
-
# end
-
#
-
# HTTP::MimeType.register_adapter 'application/json', MyJsonAdapter
-
#
-
# @param [#to_s] type
-
# @param [#encode, #decode] adapter
-
# @return [void]
-
1
def register_adapter(type, adapter)
-
1
adapters[type.to_s] = adapter
-
end
-
-
# Returns adapter associated with MIME type
-
#
-
# @param [#to_s] type
-
# @raise [Error] if no adapter found
-
# @return [Class]
-
1
def [](type)
-
adapters[normalize type] || fail(Error, "Unknown MIME type: #{type}")
-
end
-
-
# Register a shortcut for MIME type
-
#
-
# @example
-
#
-
# HTTP::MimeType.register_alias 'application/json', :json
-
#
-
# @param [#to_s] type
-
# @param [#to_sym] shortcut
-
# @return [void]
-
1
def register_alias(type, shortcut)
-
1
aliases[shortcut.to_sym] = type.to_s
-
end
-
-
# Resolves type by shortcut if possible
-
#
-
# @param [#to_s] type
-
# @return [String]
-
1
def normalize(type)
-
aliases.fetch type, type.to_s
-
end
-
-
1
private
-
-
# :nodoc:
-
1
def adapters
-
1
@adapters ||= {}
-
end
-
-
# :nodoc:
-
1
def aliases
-
1
@aliases ||= {}
-
end
-
end
-
end
-
end
-
-
# built-in mime types
-
1
require 'http/mime_type/json'
-
1
require 'forwardable'
-
1
require 'singleton'
-
-
1
module HTTP
-
1
module MimeType
-
# Base encode/decode MIME type adapter
-
1
class Adapter
-
1
include Singleton
-
-
1
class << self
-
1
extend Forwardable
-
1
def_delegators :instance, :encode, :decode
-
end
-
-
1
%w[encode decode].each do |operation|
-
2
class_eval <<-RUBY, __FILE__, __LINE__
-
def #{operation}(*)
-
fail Error, "\#{self.class} does not supports ##{operation}"
-
end
-
RUBY
-
end
-
end
-
end
-
end
-
1
require 'json'
-
1
require 'http/mime_type/adapter'
-
-
1
module HTTP
-
1
module MimeType
-
# JSON encode/decode MIME type adapter
-
1
class JSON < Adapter
-
# Encodes object to JSON
-
1
def encode(obj)
-
return obj.to_json if obj.respond_to?(:to_json)
-
::JSON.dump obj
-
end
-
-
# Decodes JSON
-
1
def decode(str)
-
::JSON.load str
-
end
-
end
-
-
1
register_adapter 'application/json', JSON
-
1
register_alias 'application/json', :json
-
end
-
end
-
1
require 'http/headers'
-
1
require 'openssl'
-
1
require 'socket'
-
-
1
module HTTP
-
1
class Options
-
# How to format the response [:object, :body, :parse_body]
-
1
attr_accessor :response
-
-
# HTTP headers to include in the request
-
1
attr_accessor :headers
-
-
# Query string params to add to the url
-
1
attr_accessor :params
-
-
# Form data to embed in the request
-
1
attr_accessor :form
-
-
# JSON data to embed in the request
-
1
attr_accessor :json
-
-
# Explicit request body of the request
-
1
attr_accessor :body
-
-
# HTTP proxy to route request
-
1
attr_accessor :proxy
-
-
# Socket classes
-
1
attr_accessor :socket_class, :ssl_socket_class
-
-
# SSL context
-
1
attr_accessor :ssl_context
-
-
# Follow redirects
-
1
attr_accessor :follow
-
-
1
protected :response=, :headers=, :proxy=, :params=, :form=, :json=, :follow=
-
-
1
@default_socket_class = TCPSocket
-
1
@default_ssl_socket_class = OpenSSL::SSL::SSLSocket
-
-
1
class << self
-
1
attr_accessor :default_socket_class, :default_ssl_socket_class
-
-
1
def new(options = {})
-
return options if options.is_a?(self)
-
super
-
end
-
end
-
-
1
def initialize(options = {})
-
@response = options[:response] || :auto
-
@proxy = options[:proxy] || {}
-
@body = options[:body]
-
@params = options[:params]
-
@form = options[:form]
-
@json = options[:json]
-
@follow = options[:follow]
-
-
@headers = HTTP::Headers.coerce(options[:headers] || {})
-
-
@socket_class = options[:socket_class] || self.class.default_socket_class
-
@ssl_socket_class = options[:ssl_socket_class] || self.class.default_ssl_socket_class
-
@ssl_context = options[:ssl_context]
-
end
-
-
1
def with_headers(headers)
-
dup do |opts|
-
opts.headers = self.headers.merge(headers)
-
end
-
end
-
-
1
%w[proxy params form json body follow].each do |method_name|
-
6
class_eval <<-RUBY, __FILE__, __LINE__
-
def with_#{method_name}(value)
-
dup { |opts| opts.#{method_name} = value }
-
end
-
RUBY
-
end
-
-
1
def [](option)
-
send(option) rescue nil
-
end
-
-
1
def merge(other)
-
h1, h2 = to_hash, other.to_hash
-
merged = h1.merge(h2) do |k, v1, v2|
-
case k
-
when :headers
-
v1.merge(v2)
-
else
-
v2
-
end
-
end
-
-
self.class.new(merged)
-
end
-
-
1
def to_hash
-
# FIXME: hardcoding these fields blows! We should have a declarative
-
# way of specifying all the options fields, and ensure they *all*
-
# get serialized here, rather than manually having to add them each time
-
{
-
:response => response,
-
:headers => headers.to_h,
-
:proxy => proxy,
-
:params => params,
-
:form => form,
-
:json => json,
-
:body => body,
-
:follow => follow,
-
:socket_class => socket_class,
-
:ssl_socket_class => ssl_socket_class,
-
:ssl_context => ssl_context
-
}
-
end
-
-
1
def dup
-
dupped = super
-
yield(dupped) if block_given?
-
dupped
-
end
-
-
1
private
-
-
1
def argument_error!(message)
-
fail(Error, message, caller[1..-1])
-
end
-
end
-
end
-
1
module HTTP
-
1
class Redirector
-
# Notifies that we reached max allowed redirect hops
-
1
class TooManyRedirectsError < ResponseError; end
-
-
# Notifies that following redirects got into an endless loop
-
1
class EndlessRedirectError < TooManyRedirectsError; end
-
-
# HTTP status codes which indicate redirects
-
1
REDIRECT_CODES = [300, 301, 302, 303, 307, 308].freeze
-
-
# :nodoc:
-
1
def initialize(options = nil)
-
options = {:max_hops => 5} unless options.respond_to?(:fetch)
-
@max_hops = options.fetch(:max_hops, 5)
-
@max_hops = false if @max_hops && 1 > @max_hops.to_i
-
end
-
-
# Follows redirects until non-redirect response found
-
1
def perform(request, response, &block)
-
reset(request, response)
-
follow(&block)
-
end
-
-
1
private
-
-
# Reset redirector state
-
1
def reset(request, response)
-
@request, @response = request, response
-
@visited = []
-
end
-
-
# Follow redirects
-
1
def follow
-
while REDIRECT_CODES.include?(@response.code)
-
@visited << @request.uri.to_s
-
-
fail TooManyRedirectsError if too_many_hops?
-
fail EndlessRedirectError if endless_loop?
-
-
uri = @response.headers['Location']
-
fail StateError, 'no Location header in redirect' unless uri
-
-
if 303 == @response.code
-
@request = @request.redirect uri, :get
-
else
-
@request = @request.redirect uri
-
end
-
-
@response = yield @request
-
end
-
-
@response
-
end
-
-
# Check if we reached max amount of redirect hops
-
1
def too_many_hops?
-
@max_hops < @visited.count if @max_hops
-
end
-
-
# Check if we got into an endless loop
-
1
def endless_loop?
-
2 < @visited.count(@visited.last)
-
end
-
end
-
end
-
1
require 'http/errors'
-
1
require 'http/headers'
-
1
require 'http/request/writer'
-
1
require 'http/version'
-
1
require 'base64'
-
1
require 'uri'
-
-
1
module HTTP
-
1
class Request
-
1
include HTTP::Headers::Mixin
-
-
# The method given was not understood
-
1
class UnsupportedMethodError < RequestError; end
-
-
# The scheme of given URI was not understood
-
1
class UnsupportedSchemeError < RequestError; end
-
-
# Default User-Agent header value
-
1
USER_AGENT = "RubyHTTPGem/#{HTTP::VERSION}".freeze
-
-
# RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
-
1
METHODS = [:options, :get, :head, :post, :put, :delete, :trace, :connect]
-
-
# RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV
-
1
METHODS.concat [:propfind, :proppatch, :mkcol, :copy, :move, :lock, :unlock]
-
-
# RFC 3648: WebDAV Ordered Collections Protocol
-
1
METHODS.concat [:orderpatch]
-
-
# RFC 3744: WebDAV Access Control Protocol
-
1
METHODS.concat [:acl]
-
-
# draft-dusseault-http-patch: PATCH Method for HTTP
-
1
METHODS.concat [:patch]
-
-
# draft-reschke-webdav-search: WebDAV Search
-
1
METHODS.concat [:search]
-
-
# Allowed schemes
-
1
SCHEMES = [:http, :https, :ws, :wss]
-
-
# Default ports of supported schemes
-
1
PORTS = {
-
:http => 80,
-
:https => 443,
-
:ws => 80,
-
:wss => 443
-
}
-
-
# Method is given as a lowercase symbol e.g. :get, :post
-
1
attr_reader :verb
-
-
# Scheme is normalized to be a lowercase symbol e.g. :http, :https
-
1
attr_reader :scheme
-
-
# The following alias may be removed in three minor versions (0.8.0) or one
-
# major version (1.0.0)
-
1
alias_method :__method__, :method
-
-
# The following method may be removed in two minor versions (0.7.0) or one
-
# major version (1.0.0)
-
1
def method(*)
-
warn "#{Kernel.caller.first}: [DEPRECATION] HTTP::Request#method is deprecated. Use #verb instead. For Object#method, use #__method__."
-
@verb
-
end
-
-
# "Request URI" as per RFC 2616
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
-
1
attr_reader :uri
-
1
attr_reader :proxy, :body, :version
-
-
# :nodoc:
-
1
def initialize(verb, uri, headers = {}, proxy = {}, body = nil, version = '1.1') # rubocop:disable ParameterLists
-
@verb = verb.to_s.downcase.to_sym
-
@uri = uri.is_a?(URI) ? uri : URI(uri.to_s)
-
@scheme = @uri.scheme.to_s.downcase.to_sym if @uri.scheme
-
-
fail(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
-
fail(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)
-
-
@proxy, @body, @version = proxy, body, version
-
-
@headers = HTTP::Headers.coerce(headers || {})
-
-
@headers['Host'] ||= default_host
-
@headers['User-Agent'] ||= USER_AGENT
-
end
-
-
# Returns new Request with updated uri
-
1
def redirect(uri, verb = @verb)
-
uri = @uri.merge uri.to_s
-
req = self.class.new(verb, uri, headers, proxy, body, version)
-
req['Host'] = req.uri.host
-
req
-
end
-
-
# Stream the request to a socket
-
1
def stream(socket)
-
include_proxy_authorization_header if using_authenticated_proxy?
-
Request::Writer.new(socket, body, headers, request_header).stream
-
end
-
-
# Is this request using a proxy?
-
1
def using_proxy?
-
proxy && proxy.keys.size >= 2
-
end
-
-
# Is this request using an authenticated proxy?
-
1
def using_authenticated_proxy?
-
proxy && proxy.keys.size == 4
-
end
-
-
# Compute and add the Proxy-Authorization header
-
1
def include_proxy_authorization_header
-
digest = Base64.encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}").chomp
-
headers['Proxy-Authorization'] = "Basic #{digest}"
-
end
-
-
# Compute HTTP request header for direct or proxy request
-
1
def request_header
-
if using_proxy?
-
"#{verb.to_s.upcase} #{uri} HTTP/#{version}"
-
else
-
path = uri.query && !uri.query.empty? ? "#{uri.path}?#{uri.query}" : uri.path
-
path = '/' if path.empty?
-
"#{verb.to_s.upcase} #{path} HTTP/#{version}"
-
end
-
end
-
-
# Host for tcp socket
-
1
def socket_host
-
using_proxy? ? proxy[:proxy_address] : uri.host
-
end
-
-
# Port for tcp socket
-
1
def socket_port
-
using_proxy? ? proxy[:proxy_port] : uri.port
-
end
-
-
1
private
-
-
# Default host (with port if needed) header value.
-
#
-
# @return [String]
-
1
def default_host
-
if PORTS[@scheme] == @uri.port
-
@uri.host
-
else
-
"#{@uri.host}:#{@uri.port}"
-
end
-
end
-
end
-
end
-
1
module HTTP
-
1
class Request
-
1
class Writer
-
# CRLF is the universal HTTP delimiter
-
1
CRLF = "\r\n"
-
-
# Types valid to be used as body source
-
1
VALID_BODY_TYPES = [String, NilClass, Enumerable]
-
-
1
def initialize(socket, body, headers, headerstart) # rubocop:disable ParameterLists
-
@body = body
-
@socket = socket
-
@headers = headers
-
@request_header = [headerstart]
-
-
validate_body_type!
-
end
-
-
# Adds headers to the request header from the headers array
-
1
def add_headers
-
@headers.each do |field, value|
-
@request_header << "#{field}: #{value}"
-
end
-
end
-
-
# Stream the request to a socket
-
1
def stream
-
send_request_header
-
send_request_body
-
end
-
-
# Adds the headers to the header array for the given request body we are working
-
# with
-
1
def add_body_type_headers
-
if @body.is_a?(String) && !@headers['Content-Length']
-
@request_header << "Content-Length: #{@body.bytesize}"
-
elsif @body.is_a?(Enumerable)
-
encoding = @headers['Transfer-Encoding']
-
if encoding == 'chunked'
-
@request_header << 'Transfer-Encoding: chunked'
-
else
-
fail(RequestError, 'invalid transfer encoding')
-
end
-
end
-
end
-
-
# Joins the headers specified in the request into a correctly formatted
-
# http request header string
-
1
def join_headers
-
# join the headers array with crlfs, stick two on the end because
-
# that ends the request header
-
@request_header.join(CRLF) + (CRLF) * 2
-
end
-
-
1
def send_request_header
-
add_headers
-
add_body_type_headers
-
header = join_headers
-
-
@socket << header
-
end
-
-
1
def send_request_body
-
if @body.is_a?(String)
-
@socket << @body
-
elsif @body.is_a?(Enumerable)
-
@body.each do |chunk|
-
@socket << chunk.bytesize.to_s(16) << CRLF
-
@socket << chunk << CRLF
-
end
-
-
@socket << '0' << CRLF * 2
-
end
-
end
-
-
1
private
-
-
1
def validate_body_type!
-
return if VALID_BODY_TYPES.any? { |type| @body.is_a? type }
-
fail RequestError, "body of wrong type: #{@body.class}"
-
end
-
end
-
end
-
end
-
1
require 'delegate'
-
1
require 'http/headers'
-
1
require 'http/content_type'
-
1
require 'http/mime_type'
-
-
1
module HTTP
-
1
class Response
-
1
include HTTP::Headers::Mixin
-
-
1
STATUS_CODES = {
-
100 => 'Continue',
-
101 => 'Switching Protocols',
-
102 => 'Processing',
-
200 => 'OK',
-
201 => 'Created',
-
202 => 'Accepted',
-
203 => 'Non-Authoritative Information',
-
204 => 'No Content',
-
205 => 'Reset Content',
-
206 => 'Partial Content',
-
207 => 'Multi-Status',
-
226 => 'IM Used',
-
300 => 'Multiple Choices',
-
301 => 'Moved Permanently',
-
302 => 'Found',
-
303 => 'See Other',
-
304 => 'Not Modified',
-
305 => 'Use Proxy',
-
306 => 'Reserved',
-
307 => 'Temporary Redirect',
-
400 => 'Bad Request',
-
401 => 'Unauthorized',
-
402 => 'Payment Required',
-
403 => 'Forbidden',
-
404 => 'Not Found',
-
405 => 'Method Not Allowed',
-
406 => 'Not Acceptable',
-
407 => 'Proxy Authentication Required',
-
408 => 'Request Timeout',
-
409 => 'Conflict',
-
410 => 'Gone',
-
411 => 'Length Required',
-
412 => 'Precondition Failed',
-
413 => 'Request Entity Too Large',
-
414 => 'Request-URI Too Long',
-
415 => 'Unsupported Media Type',
-
416 => 'Requested Range Not Satisfiable',
-
417 => 'Expectation Failed',
-
418 => "I'm a Teapot",
-
422 => 'Unprocessable Entity',
-
423 => 'Locked',
-
424 => 'Failed Dependency',
-
426 => 'Upgrade Required',
-
500 => 'Internal Server Error',
-
501 => 'Not Implemented',
-
502 => 'Bad Gateway',
-
503 => 'Service Unavailable',
-
504 => 'Gateway Timeout',
-
505 => 'HTTP Version Not Supported',
-
506 => 'Variant Also Negotiates',
-
507 => 'Insufficient Storage',
-
510 => 'Not Extended'
-
}
-
1
STATUS_CODES.freeze
-
-
53
SYMBOL_TO_STATUS_CODE = Hash[STATUS_CODES.map { |code, msg| [msg.downcase.gsub(/\s|-/, '_').to_sym, code] }]
-
1
SYMBOL_TO_STATUS_CODE.freeze
-
-
1
attr_reader :status
-
1
attr_reader :body
-
1
attr_reader :uri
-
-
# Status aliases! TIMTOWTDI!!! (Want to be idiomatic? Just use status :)
-
1
alias_method :code, :status
-
1
alias_method :status_code, :status
-
-
1
def initialize(status, version, headers, body, uri = nil) # rubocop:disable ParameterLists
-
@status, @version, @body, @uri = status, version, body, uri
-
@headers = HTTP::Headers.coerce(headers || {})
-
end
-
-
# Obtain the 'Reason-Phrase' for the response
-
1
def reason
-
STATUS_CODES[@status]
-
end
-
-
# Returns an Array ala Rack: `[status, headers, body]`
-
1
def to_a
-
[status, headers.to_h, body.to_s]
-
end
-
-
# Return the response body as a string
-
1
def to_s
-
body.to_s
-
end
-
1
alias_method :to_str, :to_s
-
-
# Flushes body and returns self-reference
-
1
def flush
-
body.to_s
-
self
-
end
-
-
# Parsed Content-Type header
-
# @return [HTTP::ContentType]
-
1
def content_type
-
@content_type ||= ContentType.parse headers['Content-Type']
-
end
-
-
# MIME type of response (if any)
-
# @return [String, nil]
-
1
def mime_type
-
@mime_type ||= content_type.mime_type
-
end
-
-
# Charset of response (if any)
-
# @return [String, nil]
-
1
def charset
-
@charset ||= content_type.charset
-
end
-
-
# Parse response body with corresponding MIME type adapter.
-
#
-
# @param [#to_s] as Parse as given MIME type
-
# instead of the one determined from headers
-
# @raise [Error] if adapter not found
-
# @return [Object]
-
1
def parse(as = nil)
-
MimeType[as || mime_type].decode to_s
-
end
-
-
# Inspect a response
-
1
def inspect
-
"#<#{self.class}/#{@version} #{status} #{reason} headers=#{headers.inspect}>"
-
end
-
end
-
end
-
1
require 'forwardable'
-
1
require 'http/client'
-
-
1
module HTTP
-
1
class Response
-
# A streamable response body, also easily converted into a string
-
1
class Body
-
1
extend Forwardable
-
1
include Enumerable
-
1
def_delegator :to_s, :empty?
-
-
1
def initialize(client)
-
@client = client
-
@streaming = nil
-
@contents = nil
-
end
-
-
# Read up to length bytes, but return any data that's available
-
# @see HTTP::Client#readpartial
-
1
def readpartial(*args)
-
stream!
-
@client.readpartial(*args)
-
end
-
-
# Iterate over the body, allowing it to be enumerable
-
1
def each
-
while (chunk = readpartial)
-
yield chunk
-
end
-
end
-
-
# Eagerly consume the entire body as a string
-
1
def to_s
-
return @contents if @contents
-
fail StateError, 'body is being streamed' unless @streaming.nil?
-
-
begin
-
@streaming = false
-
@contents = ''
-
while (chunk = @client.readpartial)
-
@contents << chunk
-
end
-
rescue
-
@contents = nil
-
raise
-
end
-
-
@contents
-
end
-
1
alias_method :to_str, :to_s
-
-
# Assert that the body is actively being streamed
-
1
def stream!
-
fail StateError, 'body has already been consumed' if @streaming == false
-
@streaming = true
-
end
-
-
# Easier to interpret string inspect
-
1
def inspect
-
"#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>"
-
end
-
end
-
end
-
end
-
1
module HTTP
-
1
class Response
-
1
class Parser
-
1
attr_reader :headers
-
-
1
def initialize
-
@parser = HTTP::Parser.new(self)
-
reset
-
end
-
-
1
def add(data)
-
@parser << data
-
end
-
1
alias_method :<<, :add
-
-
1
def headers?
-
!!@headers
-
end
-
-
1
def http_version
-
@parser.http_version.join('.')
-
end
-
-
1
def status_code
-
@parser.status_code
-
end
-
-
#
-
# HTTP::Parser callbacks
-
#
-
-
1
def on_headers_complete(headers)
-
@headers = headers
-
end
-
-
1
def on_body(chunk)
-
if @chunk
-
@chunk << chunk
-
else
-
@chunk = chunk
-
end
-
end
-
-
1
def chunk
-
chunk, @chunk = @chunk, nil
-
chunk
-
end
-
-
1
def on_message_complete
-
@finished = true
-
end
-
-
1
def reset
-
@parser.reset!
-
-
@finished = false
-
@headers = nil
-
@chunk = nil
-
end
-
-
1
def finished?
-
@finished
-
end
-
end
-
end
-
end
-
1
module HTTP
-
1
VERSION = '0.6.2'
-
end
-
1
require 'http_parser'
-
1
$:.unshift File.expand_path('../', __FILE__)
-
1
require 'ruby_http_parser'
-
-
1
Http = HTTP
-
-
1
module HTTP
-
1
class Parser
-
1
class << self
-
1
attr_reader :default_header_value_type
-
-
1
def default_header_value_type=(val)
-
1
if (val != :mixed && val != :strings && val != :arrays)
-
raise ArgumentError, "Invalid header value type"
-
end
-
1
@default_header_value_type = val
-
end
-
end
-
end
-
end
-
-
1
HTTP::Parser.default_header_value_type = :mixed
-
# encoding: utf-8
-
-
1
require 'monitor'
-
1
require 'thread_safe'
-
-
1
require 'memoizable/instance_methods'
-
1
require 'memoizable/method_builder'
-
1
require 'memoizable/module_methods'
-
1
require 'memoizable/memory'
-
1
require 'memoizable/version'
-
-
# Allow methods to be memoized
-
1
module Memoizable
-
1
include InstanceMethods
-
-
# Default freezer
-
42
Freezer = lambda { |object| object.freeze }.freeze
-
-
# Hook called when module is included
-
#
-
# @param [Module] descendant
-
# the module or class including Memoizable
-
#
-
# @return [self]
-
#
-
# @api private
-
1
def self.included(descendant)
-
7
super
-
7
descendant.extend(ModuleMethods)
-
end
-
1
private_class_method :included
-
-
end # Memoizable
-
# encoding: utf-8
-
-
1
module Memoizable
-
-
# Methods mixed in to memoizable instances
-
1
module InstanceMethods
-
-
# Freeze the object
-
#
-
# @example
-
# object.freeze # object is now frozen
-
#
-
# @return [Object]
-
#
-
# @api public
-
1
def freeze
-
2
memoized_method_cache # initialize method cache
-
2
super
-
end
-
-
# Sets a memoized value for a method
-
#
-
# @example
-
# object.memoize(hash: 12345)
-
#
-
# @param [Hash{Symbol => Object}] data
-
# the data to memoize
-
#
-
# @return [self]
-
#
-
# @api public
-
1
def memoize(data)
-
data.each { |name, value| memoized_method_cache[name] = value }
-
self
-
end
-
-
1
private
-
-
# The memoized method results
-
#
-
# @return [Hash]
-
#
-
# @api private
-
1
def memoized_method_cache
-
53
@_memoized_method_cache ||= Memory.new
-
end
-
-
end # InstanceMethods
-
end # Memoizable
-
# encoding: utf-8
-
-
1
module Memoizable
-
-
# Storage for memoized methods
-
1
class Memory
-
-
# Initialize the memory storage for memoized methods
-
#
-
# @param [ThreadSafe::Cache] memory
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def initialize
-
39
@memory = ThreadSafe::Cache.new
-
39
@monitor = Monitor.new
-
39
freeze
-
end
-
-
# Get the value from memory
-
#
-
# @param [Symbol] name
-
#
-
# @return [Object]
-
#
-
# @api public
-
1
def [](name)
-
@memory.fetch(name) do
-
fail NameError, "No method #{name} is memoized"
-
end
-
end
-
-
# Store the value in memory
-
#
-
# @param [Symbol] name
-
# @param [Object] value
-
#
-
# @return [undefined]
-
#
-
# @api public
-
1
def []=(name, value)
-
90
memoized = true
-
90
@memory.compute_if_absent(name) do
-
90
memoized = false
-
90
value
-
end
-
90
fail ArgumentError, "The method #{name} is already memoized" if memoized
-
end
-
-
# Fetch the value from memory, or store it if it does not exist
-
#
-
# @param [Symbol] name
-
#
-
# @yieldreturn [Object]
-
# the value to memoize
-
#
-
# @api public
-
1
def fetch(name)
-
51
@memory.fetch(name) do # check for the key
-
41
@monitor.synchronize do # acquire a lock if the key is not found
-
41
@memory.fetch(name) do # recheck under lock
-
41
self[name] = yield # set the value
-
end
-
end
-
end
-
end
-
-
# Test if the name has a value in memory
-
#
-
# @param [Symbol] name
-
#
-
# @return [Boolean]
-
#
-
# @api public
-
1
def key?(name)
-
@memory.key?(name)
-
end
-
-
# A hook that allows Marshal to dump the object
-
#
-
# @return [Hash]
-
# A hash used to populate the internal memory
-
#
-
# @api public
-
1
def marshal_dump
-
@memory.marshal_dump
-
end
-
-
# A hook that allows Marshal to load the object
-
#
-
# @param [Hash] hash
-
# A hash used to populate the internal memory
-
#
-
# @return [undefined]
-
#
-
# @api public
-
1
def marshal_load(hash)
-
initialize
-
@memory.marshal_load(hash)
-
end
-
-
end # Memory
-
end # Memoizable
-
# encoding: utf-8
-
-
1
module Memoizable
-
-
# Build the memoized method
-
1
class MethodBuilder
-
-
# Raised when the method arity is invalid
-
1
class InvalidArityError < ArgumentError
-
-
# Initialize an invalid arity exception
-
#
-
# @param [Module] descendant
-
# @param [Symbol] method
-
# @param [Integer] arity
-
#
-
# @api private
-
1
def initialize(descendant, method, arity)
-
super("Cannot memoize #{descendant}##{method}, its arity is #{arity}")
-
end
-
-
end # InvalidArityError
-
-
# Raised when a block is passed to a memoized method
-
1
class BlockNotAllowedError < ArgumentError
-
-
# Initialize a block not allowed exception
-
#
-
# @param [Module] descendant
-
# @param [Symbol] method
-
#
-
# @api private
-
1
def initialize(descendant, method)
-
super("Cannot pass a block to #{descendant}##{method}, it is memoized")
-
end
-
-
end # BlockNotAllowedError
-
-
# The original method before memoization
-
#
-
# @return [UnboundMethod]
-
#
-
# @api public
-
1
attr_reader :original_method
-
-
# Initialize an object to build a memoized method
-
#
-
# @param [Module] descendant
-
# @param [Symbol] method_name
-
# @param [#call] freezer
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def initialize(descendant, method_name, freezer)
-
49
@descendant = descendant
-
49
@method_name = method_name
-
49
@freezer = freezer
-
49
@original_visibility = visibility
-
49
@original_method = @descendant.instance_method(@method_name)
-
49
assert_arity(@original_method.arity)
-
end
-
-
# Build a new memoized method
-
#
-
# @example
-
# method_builder.call # => creates new method
-
#
-
# @return [MethodBuilder]
-
#
-
# @api public
-
1
def call
-
49
remove_original_method
-
49
create_memoized_method
-
49
set_method_visibility
-
49
self
-
end
-
-
1
private
-
-
# Assert the method arity is zero
-
#
-
# @param [Integer] arity
-
#
-
# @return [undefined]
-
#
-
# @raise [InvalidArityError]
-
#
-
# @api private
-
1
def assert_arity(arity)
-
49
if arity.nonzero?
-
fail InvalidArityError.new(@descendant, @method_name, arity)
-
end
-
end
-
-
# Remove the original method
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def remove_original_method
-
49
name = @method_name
-
98
@descendant.module_eval { undef_method(name) }
-
end
-
-
# Create a new memoized method
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def create_memoized_method
-
49
name, method, freezer = @method_name, @original_method, @freezer
-
49
@descendant.module_eval do
-
49
define_method(name) do |&block|
-
51
fail BlockNotAllowedError.new(self.class, name) if block
-
51
memoized_method_cache.fetch(name) do
-
41
freezer.call(method.bind(self).call)
-
end
-
end
-
end
-
end
-
-
# Set the memoized method visibility to match the original method
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def set_method_visibility
-
49
@descendant.send(@original_visibility, @method_name)
-
end
-
-
# Get the visibility of the original method
-
#
-
# @return [Symbol]
-
#
-
# @api private
-
1
def visibility
-
49
if @descendant.private_method_defined?(@method_name) then :private
-
49
elsif @descendant.protected_method_defined?(@method_name) then :protected
-
49
else :public
-
end
-
end
-
-
end # MethodBuilder
-
end # Memoizable
-
# encoding: utf-8
-
-
1
module Memoizable
-
-
# Methods mixed in to memoizable singleton classes
-
1
module ModuleMethods
-
-
# Return default deep freezer
-
#
-
# @return [#call]
-
#
-
# @api private
-
1
def freezer
-
49
Freezer
-
end
-
-
# Memoize a list of methods
-
#
-
# @example
-
# memoize :hash
-
#
-
# @param [Array<Symbol>] methods
-
# a list of methods to memoize
-
#
-
# @return [self]
-
#
-
# @api public
-
1
def memoize(*methods)
-
49
methods.each(&method(:memoize_method))
-
49
self
-
end
-
-
# Test if an instance method is memoized
-
#
-
# @example
-
# class Foo
-
# include Memoizable
-
#
-
# def bar
-
# end
-
# memoize :bar
-
# end
-
#
-
# Foo.memoized?(:bar) # true
-
# Foo.memoized?(:baz) # false
-
#
-
# @param [Symbol] name
-
#
-
# @return [Boolean]
-
# true if method is memoized, false if not
-
#
-
# @api private
-
1
def memoized?(name)
-
memoized_methods.key?(name)
-
end
-
-
# Return unmemoized instance method
-
#
-
# @example
-
#
-
# class Foo
-
# include Memoizable
-
#
-
# def bar
-
# end
-
# memoize :bar
-
# end
-
#
-
# Foo.unmemoized_instance_method(:bar)
-
#
-
# @param [Symbol] name
-
#
-
# @return [UnboundMethod]
-
# the memoized method
-
#
-
# @raise [NameError]
-
# raised if the method is unknown
-
#
-
# @api public
-
1
def unmemoized_instance_method(name)
-
memoized_methods[name].original_method
-
end
-
-
1
private
-
-
# Hook called when module is included
-
#
-
# @param [Module] descendant
-
# the module including ModuleMethods
-
#
-
# @return [self]
-
#
-
# @api private
-
1
def included(descendant)
-
2
super
-
4
descendant.module_eval { include Memoizable }
-
end
-
-
# Memoize the named method
-
#
-
# @param [Symbol] method_name
-
# a method name to memoize
-
#
-
# @return [undefined]
-
#
-
# @api private
-
1
def memoize_method(method_name)
-
49
memoized_methods[method_name] = MethodBuilder.new(
-
self,
-
method_name,
-
freezer
-
).call
-
end
-
-
# Return method builder registry
-
#
-
# @return [Hash<Symbol, MethodBuilder>]
-
#
-
# @api private
-
1
def memoized_methods
-
49
@_memoized_methods ||= Memory.new
-
end
-
-
end # ModuleMethods
-
end # Memoizable
-
# encoding: utf-8
-
-
1
module Memoizable
-
-
# Gem version
-
1
VERSION = '0.4.2'.freeze
-
-
end # Memoizable
-
#--
-
# Copyright (c) 2007-2012 Nick Sieger.
-
# See the file README.txt included with the distribution for
-
# software license details.
-
#++
-
-
# Concatenate together multiple IO objects into a single, composite IO object
-
# for purposes of reading as a single stream.
-
#
-
# Usage:
-
#
-
# crio = CompositeReadIO.new(StringIO.new('one'), StringIO.new('two'), StringIO.new('three'))
-
# puts crio.read # => "onetwothree"
-
#
-
1
class CompositeReadIO
-
# Create a new composite-read IO from the arguments, all of which should
-
# respond to #read in a manner consistent with IO.
-
1
def initialize(*ios)
-
@ios = ios.flatten
-
@index = 0
-
end
-
-
# Read from IOs in order until `length` bytes have been received.
-
1
def read(length = nil, outbuf = nil)
-
got_result = false
-
outbuf = outbuf ? outbuf.replace("") : ""
-
-
while io = current_io
-
if result = io.read(length)
-
got_result ||= !result.nil?
-
result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
-
outbuf << result
-
length -= result.length if length
-
break if length == 0
-
end
-
advance_io
-
end
-
(!got_result && length) ? nil : outbuf
-
end
-
-
1
def rewind
-
@ios.each { |io| io.rewind }
-
@index = 0
-
end
-
-
1
private
-
-
1
def current_io
-
@ios[@index]
-
end
-
-
1
def advance_io
-
@index += 1
-
end
-
end
-
-
# Convenience methods for dealing with files and IO that are to be uploaded.
-
1
class UploadIO
-
# Create an upload IO suitable for including in the params hash of a
-
# Net::HTTP::Post::Multipart.
-
#
-
# Can take two forms. The first accepts a filename and content type, and
-
# opens the file for reading (to be closed by finalizer).
-
#
-
# The second accepts an already-open IO, but also requires a third argument,
-
# the filename from which it was opened (particularly useful/recommended if
-
# uploading directly from a form in a framework, which often save the file to
-
# an arbitrarily named RackMultipart file in /tmp).
-
#
-
# Usage:
-
#
-
# UploadIO.new("file.txt", "text/plain")
-
# UploadIO.new(file_io, "text/plain", "file.txt")
-
#
-
1
attr_reader :content_type, :original_filename, :local_path, :io, :opts
-
-
1
def initialize(filename_or_io, content_type, filename = nil, opts = {})
-
io = filename_or_io
-
local_path = ""
-
if io.respond_to? :read
-
# in Ruby 1.9.2, StringIOs no longer respond to path
-
# (since they respond to :length, so we don't need their local path, see parts.rb:41)
-
local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path"
-
else
-
io = File.open(filename_or_io)
-
local_path = filename_or_io
-
end
-
filename ||= local_path
-
-
@content_type = content_type
-
@original_filename = File.basename(filename)
-
@local_path = local_path
-
@io = io
-
@opts = opts
-
end
-
-
1
def self.convert!(io, content_type, original_filename, local_path)
-
raise ArgumentError, "convert! has been removed. You must now wrap IOs using:\nUploadIO.new(filename_or_io, content_type, filename=nil)\nPlease update your code."
-
end
-
-
1
def method_missing(*args)
-
@io.send(*args)
-
end
-
-
1
def respond_to?(meth, include_all = false)
-
@io.respond_to?(meth, include_all) || super(meth, include_all)
-
end
-
end
-
#--
-
# Copyright (c) 2007-2013 Nick Sieger.
-
# See the file README.txt included with the distribution for
-
# software license details.
-
#++
-
-
1
module Parts
-
1
module Part #:nodoc:
-
1
def self.new(boundary, name, value, headers = {})
-
headers ||= {} # avoid nil values
-
if file?(value)
-
FilePart.new(boundary, name, value, headers)
-
else
-
ParamPart.new(boundary, name, value, headers)
-
end
-
end
-
-
1
def self.file?(value)
-
value.respond_to?(:content_type) && value.respond_to?(:original_filename)
-
end
-
-
1
def length
-
@part.length
-
end
-
-
1
def to_io
-
@io
-
end
-
end
-
-
1
class ParamPart
-
1
include Part
-
1
def initialize(boundary, name, value, headers = {})
-
@part = build_part(boundary, name, value, headers)
-
@io = StringIO.new(@part)
-
end
-
-
1
def length
-
@part.bytesize
-
end
-
-
1
def build_part(boundary, name, value, headers = {})
-
part = ''
-
part << "--#{boundary}\r\n"
-
part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
-
part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"]
-
part << "\r\n"
-
part << "#{value}\r\n"
-
end
-
end
-
-
# Represents a part to be filled from file IO.
-
1
class FilePart
-
1
include Part
-
1
attr_reader :length
-
1
def initialize(boundary, name, io, headers = {})
-
file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
-
@head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
-
io.respond_to?(:opts) ? io.opts.merge(headers) : headers)
-
@foot = "\r\n"
-
@length = @head.bytesize + file_length + @foot.length
-
@io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
-
end
-
-
1
def build_head(boundary, name, filename, type, content_len, opts = {}, headers = {})
-
trans_encoding = opts["Content-Transfer-Encoding"] || "binary"
-
content_disposition = opts["Content-Disposition"] || "form-data"
-
-
part = ''
-
part << "--#{boundary}\r\n"
-
part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
-
part << "Content-Length: #{content_len}\r\n"
-
if content_id = opts["Content-ID"]
-
part << "Content-ID: #{content_id}\r\n"
-
end
-
-
if headers["Content-Type"] != nil
-
part << "Content-Type: " + headers["Content-Type"] + "\r\n"
-
else
-
part << "Content-Type: #{type}\r\n"
-
end
-
-
part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
-
part << "\r\n"
-
end
-
end
-
-
# Represents the epilogue or closing boundary.
-
1
class EpiloguePart
-
1
include Part
-
1
def initialize(boundary)
-
@part = "--#{boundary}--\r\n\r\n"
-
@io = StringIO.new(@part)
-
end
-
end
-
end
-
1
require 'naught/version'
-
1
require 'naught/null_class_builder'
-
1
require 'naught/null_class_builder/commands'
-
-
1
module Naught
-
1
def self.build(&customization_block)
-
1
builder = NullClassBuilder.new
-
1
builder.customize(&customization_block)
-
1
builder.generate_class
-
end
-
1
module NullObjectTag
-
end
-
end
-
1
module Naught
-
1
if defined? ::BasicObject
-
1
class BasicObject < ::BasicObject
-
end
-
else
-
class BasicObject #:nodoc:
-
keep = %w[
-
! != == __id__ __send__ equal? instance_eval instance_exec
-
method_missing singleton_method_added singleton_method_removed
-
singleton_method_undefined
-
]
-
instance_methods.each do |method_name|
-
undef_method(method_name) unless keep.include?(method_name)
-
end
-
end
-
end
-
end
-
1
module Naught
-
1
module Conversions
-
1
def self.included(null_class)
-
1
unless class_variable_defined?(:@@included) && @@included
-
1
@@null_class = null_class
-
1
@@null_equivs = null_class::NULL_EQUIVS
-
1
@@included = true
-
end
-
1
super
-
end
-
-
1
def Null(object = :nothing_passed)
-
case object
-
when NullObjectTag
-
object
-
when :nothing_passed, *@@null_equivs
-
@@null_class.get(:caller => caller(1))
-
else
-
fail ArgumentError, "#{object.inspect} is not null!"
-
end
-
end
-
-
1
def Maybe(object = nil)
-
object = yield if block_given?
-
case object
-
when NullObjectTag
-
object
-
when *@@null_equivs
-
@@null_class.get(:caller => caller(1))
-
else
-
object
-
end
-
end
-
-
1
def Just(object = nil)
-
object = yield if block_given?
-
case object
-
when NullObjectTag, *@@null_equivs
-
fail ArgumentError, "Null value: #{object.inspect}"
-
else
-
object
-
end
-
end
-
-
1
def Actual(object = nil)
-
object = yield if block_given?
-
case object
-
when NullObjectTag
-
nil
-
else
-
object
-
end
-
end
-
end
-
end
-
1
require 'naught/basic_object'
-
1
require 'naught/conversions'
-
-
1
module Naught
-
1
class NullClassBuilder
-
# make sure this module exists
-
1
module Commands
-
end
-
-
1
attr_accessor :base_class, :inspect_proc, :interface_defined
-
-
1
def initialize
-
1
@interface_defined = false
-
1
@base_class = Naught::BasicObject
-
1
@inspect_proc = lambda { '<null>' }
-
1
@stub_strategy = :stub_method_returning_nil
-
1
define_basic_methods
-
end
-
-
1
def interface_defined?
-
1
!!@interface_defined
-
end
-
-
1
def customize(&customization_block)
-
1
return unless customization_block
-
1
customization_module.module_exec(self, &customization_block)
-
end
-
-
1
def customization_module
-
2
@customization_module ||= Module.new
-
end
-
-
1
def null_equivalents
-
1
@null_equivalents ||= [nil]
-
end
-
-
1
def generate_class
-
1
respond_to_any_message unless interface_defined?
-
1
generation_mod = Module.new
-
1
customization_mod = customization_module # get a local binding
-
1
builder = self
-
-
1
apply_operations(operations, generation_mod)
-
-
1
null_class = Class.new(@base_class) do
-
1
const_set :GeneratedMethods, generation_mod
-
1
const_set :Customizations, customization_mod
-
1
const_set :NULL_EQUIVS, builder.null_equivalents
-
1
include Conversions
-
1
remove_const :NULL_EQUIVS
-
1
Conversions.instance_methods.each do |instance_method|
-
4
undef_method(instance_method)
-
end
-
1
const_set :Conversions, Conversions
-
-
1
include NullObjectTag
-
1
include generation_mod
-
1
include customization_mod
-
end
-
-
1
apply_operations(class_operations, null_class)
-
-
1
null_class
-
end
-
-
1
def method_missing(method_name, *args, &block)
-
3
command_name = command_name_for_method(method_name)
-
3
if Commands.const_defined?(command_name)
-
3
command_class = Commands.const_get(command_name)
-
3
command_class.new(self, *args, &block).call
-
else
-
super
-
end
-
end
-
-
1
if RUBY_VERSION >= '1.9'
-
1
def respond_to_missing?(method_name, include_private = false)
-
command_name = command_name_for_method(method_name)
-
Commands.const_defined?(command_name) || super
-
rescue NameError
-
super
-
end
-
else
-
def respond_to?(method_name, include_private = false)
-
command_name = command_name_for_method(method_name)
-
Commands.const_defined?(command_name) || super
-
rescue NameError
-
super
-
end
-
end
-
-
############################################################################
-
# Builder API
-
#
-
# See also the contents of lib/naught/null_class_builder/commands
-
############################################################################
-
-
1
def black_hole
-
1
@stub_strategy = :stub_method_returning_self
-
end
-
-
1
def respond_to_any_message
-
1
defer(:prepend => true) do |subject|
-
1
subject.module_eval do
-
1
def respond_to?(*)
-
true
-
end
-
end
-
1
stub_method(subject, :method_missing)
-
end
-
1
@interface_defined = true
-
end
-
-
1
def defer(options = {}, &deferred_operation)
-
6
list = options[:class] ? class_operations : operations
-
6
if options[:prepend]
-
1
list.unshift(deferred_operation)
-
else
-
5
list << deferred_operation
-
end
-
end
-
-
1
def stub_method(subject, name)
-
1
send(@stub_strategy, subject, name)
-
end
-
-
1
private
-
-
1
def define_basic_methods
-
1
define_basic_instance_methods
-
1
define_basic_class_methods
-
end
-
-
1
def apply_operations(operations, module_or_class)
-
2
operations.each do |operation|
-
6
operation.call(module_or_class)
-
end
-
end
-
-
1
def define_basic_instance_methods
-
1
defer do |subject|
-
1
subject.module_exec(@inspect_proc) do |inspect_proc|
-
1
define_method(:inspect, &inspect_proc)
-
1
def initialize(*)
-
end
-
end
-
end
-
end
-
-
1
def define_basic_class_methods
-
1
defer(:class => true) do |subject|
-
1
subject.module_eval do
-
1
class << self
-
1
alias_method :get, :new
-
end
-
1
klass = self
-
1
define_method(:class) { klass }
-
end
-
end
-
end
-
-
1
def class_operations
-
2
@class_operations ||= []
-
end
-
-
1
def operations
-
6
@operations ||= []
-
end
-
-
1
def stub_method_returning_nil(subject, name)
-
subject.module_eval do
-
define_method(name) { |*| nil }
-
end
-
end
-
-
1
def stub_method_returning_self(subject, name)
-
1
subject.module_eval do
-
6
define_method(name) { |*| self }
-
end
-
end
-
-
1
def command_name_for_method(method_name)
-
11
method_name.to_s.gsub(/(?:^|_)([a-z])/) { Regexp.last_match[1].upcase }
-
end
-
end
-
end
-
1
module Naught
-
1
class NullClassBuilder
-
1
class Command
-
1
attr_reader :builder
-
-
1
def initialize(builder)
-
3
@builder = builder
-
end
-
-
1
def call
-
fail NotImplementedError,
-
'Method #call should be overriden in child classes'
-
end
-
-
1
def defer(options = {}, &block)
-
3
@builder.defer(options, &block)
-
end
-
end
-
end
-
end
-
1
require 'naught/null_class_builder/commands/define_explicit_conversions'
-
1
require 'naught/null_class_builder/commands/define_implicit_conversions'
-
1
require 'naught/null_class_builder/commands/pebble'
-
1
require 'naught/null_class_builder/commands/predicates_return'
-
1
require 'naught/null_class_builder/commands/singleton'
-
1
require 'naught/null_class_builder/commands/traceable'
-
1
require 'naught/null_class_builder/commands/mimic'
-
1
require 'naught/null_class_builder/commands/impersonate'
-
1
require 'forwardable'
-
1
require 'naught/null_class_builder/command'
-
-
1
module Naught::NullClassBuilder::Commands
-
1
class DefineExplicitConversions < ::Naught::NullClassBuilder::Command
-
1
def call
-
1
defer do |subject|
-
1
subject.module_eval do
-
1
extend Forwardable
-
1
def_delegators :nil, :to_a, :to_c, :to_f, :to_h, :to_i, :to_r, :to_s
-
end
-
end
-
end
-
end
-
end
-
1
require 'naught/null_class_builder/command'
-
-
1
module Naught::NullClassBuilder::Commands
-
1
class DefineImplicitConversions < ::Naught::NullClassBuilder::Command
-
1
def call
-
1
defer do |subject|
-
1
subject.module_eval do
-
1
def to_ary
-
[]
-
end
-
-
1
def to_str
-
''
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module Naught::NullClassBuilder::Commands
-
1
class Impersonate < Naught::NullClassBuilder::Commands::Mimic
-
1
def initialize(builder, class_to_impersonate, options = {})
-
super
-
builder.base_class = class_to_impersonate
-
end
-
end
-
end
-
1
require 'naught/basic_object'
-
1
require 'naught/null_class_builder/command'
-
-
1
module Naught::NullClassBuilder::Commands
-
1
class Mimic < Naught::NullClassBuilder::Command
-
1
attr_reader :class_to_mimic, :include_super
-
-
1
def initialize(builder, class_to_mimic, options = {})
-
super(builder)
-
-
@class_to_mimic = class_to_mimic
-
@include_super = options.fetch(:include_super) { true }
-
-
builder.base_class = root_class_of(class_to_mimic)
-
builder.inspect_proc = lambda { "<null:#{class_to_mimic}>" }
-
builder.interface_defined = true
-
end
-
-
1
def call
-
defer do |subject|
-
methods_to_stub.each do |method_name|
-
builder.stub_method(subject, method_name)
-
end
-
end
-
end
-
-
1
private
-
-
1
def root_class_of(klass)
-
klass.ancestors.include?(Object) ? Object : Naught::BasicObject
-
end
-
-
1
def methods_to_stub
-
class_to_mimic.instance_methods(include_super) - Object.instance_methods
-
end
-
end
-
end
-
1
require 'naught/null_class_builder/command'
-
-
1
module Naught
-
1
class NullClassBuilder
-
1
module Commands
-
1
class Pebble < ::Naught::NullClassBuilder::Command
-
1
def initialize(builder, output = $stdout)
-
@builder = builder
-
@output = output
-
end
-
-
1
def call
-
defer do |subject|
-
subject.module_exec(@output) do |output|
-
-
define_method(:method_missing) do |method_name, *args, &block|
-
pretty_args = args.collect(&:inspect).join(', ').gsub("\"", "'")
-
output.puts "#{method_name}(#{pretty_args}) from #{parse_caller}"
-
self
-
end
-
-
def parse_caller
-
caller = Kernel.caller(2).first
-
method_name = caller.match(/\`([\w\s]+(\(\d+\s\w+\))?[\w\s]*)/)
-
method_name ? method_name[1] : caller
-
end
-
private :parse_caller
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'naught/null_class_builder/command'
-
-
1
module Naught::NullClassBuilder::Commands
-
1
class PredicatesReturn < Naught::NullClassBuilder::Command
-
1
def initialize(builder, return_value)
-
1
super(builder)
-
1
@predicate_return_value = return_value
-
end
-
-
1
def call
-
1
defer do |subject|
-
1
define_method_missing(subject)
-
1
define_predicate_methods(subject)
-
end
-
end
-
-
1
private
-
-
1
def define_method_missing(subject)
-
1
subject.module_exec(@predicate_return_value) do |return_value|
-
1
if subject.method_defined?(:method_missing)
-
1
original_method_missing = instance_method(:method_missing)
-
1
define_method(:method_missing) do |method_name, *args, &block|
-
5
if method_name.to_s.end_with?('?')
-
return_value
-
else
-
5
original_method_missing.bind(self).call(method_name, *args, &block)
-
end
-
end
-
end
-
end
-
end
-
-
1
def define_predicate_methods(subject)
-
1
subject.module_exec(@predicate_return_value) do |return_value|
-
1
instance_methods.each do |method_name|
-
12
if method_name.to_s.end_with?('?')
-
1
define_method(method_name) do |*|
-
return_value
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'naught/null_class_builder/command'
-
-
1
module Naught::NullClassBuilder::Commands
-
1
class Singleton < Naught::NullClassBuilder::Command
-
1
def call
-
defer(:class => true) do |subject|
-
require 'singleton'
-
subject.module_eval do
-
include ::Singleton
-
-
def self.get(*)
-
instance
-
end
-
-
%w(dup clone).each do |method_name|
-
define_method method_name do
-
self
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'naught/null_class_builder/command'
-
-
1
module Naught::NullClassBuilder::Commands
-
1
class Traceable < Naught::NullClassBuilder::Command
-
1
def call
-
defer do |subject|
-
subject.module_eval do
-
attr_reader :__file__, :__line__
-
-
def initialize(options = {})
-
range = (RUBY_VERSION.to_f == 1.9 && RUBY_PLATFORM != 'java') ? 4 : 3
-
backtrace = options.fetch(:caller) { Kernel.caller(range) }
-
@__file__, line, _ = backtrace[0].split(':')
-
@__line__ = line.to_i
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module Naught
-
1
VERSION = '1.0.0'
-
end
-
1
require 'rspec/core'
-
1
require 'rspec/version'
-
-
1
module RSpec # :nodoc:
-
1
module Version # :nodoc:
-
1
STRING = '3.1.0'
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/helpers"
-
1
require 'stringio'
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# RSpec's built-in formatters are all subclasses of RSpec::Core::Formatters::BaseTextFormatter.
-
#
-
# @see RSpec::Core::Formatters::BaseTextFormatter
-
# @see RSpec::Core::Reporter
-
# @see RSpec::Core::Formatters::Protocol
-
1
class BaseFormatter
-
# all formatters inheriting from this formatter will receive these notifications
-
1
Formatters.register self, :start, :example_group_started, :close
-
1
attr_accessor :example_group
-
1
attr_reader :output
-
-
# @api public
-
# @param output [IO] the formatter output
-
# @see RSpec::Core::Formatters::Protocol#initialize
-
1
def initialize(output)
-
1
@output = output || StringIO.new
-
1
@example_group = nil
-
end
-
-
# @api public
-
#
-
# @param notification [StartNotification]
-
# @see RSpec::Core::Formatters::Protocol#start
-
1
def start(notification)
-
1
start_sync_output
-
1
@example_count = notification.count
-
end
-
-
# @api public
-
#
-
# @param notification [GroupNotification] containing example_group subclass of `RSpec::Core::ExampleGroup`
-
# @see RSpec::Core::Formatters::Protocol#example_group_started
-
1
def example_group_started(notification)
-
54
@example_group = notification.group
-
end
-
-
# @api public
-
#
-
# @param notification [NullNotification]
-
# @see RSpec::Core::Formatters::Protocol#close
-
1
def close(_notification)
-
restore_sync_output
-
end
-
-
1
private
-
-
1
def start_sync_output
-
1
@old_sync, output.sync = output.sync, true if output_supports_sync
-
end
-
-
1
def restore_sync_output
-
output.sync = @old_sync if output_supports_sync && !output.closed?
-
end
-
-
1
def output_supports_sync
-
1
output.respond_to?(:sync=)
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/base_formatter"
-
1
RSpec::Support.require_rspec_core "formatters/console_codes"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# Base for all of RSpec's built-in formatters. See RSpec::Core::Formatters::BaseFormatter
-
# to learn more about all of the methods called by the reporter.
-
#
-
# @see RSpec::Core::Formatters::BaseFormatter
-
# @see RSpec::Core::Reporter
-
1
class BaseTextFormatter < BaseFormatter
-
1
Formatters.register self,
-
:message, :dump_summary, :dump_failures, :dump_pending, :seed
-
-
# @method message
-
# @api public
-
#
-
# Used by the reporter to send messages to the output stream.
-
#
-
# @param notification [MessageNotification] containing message
-
1
def message(notification)
-
output.puts notification.message
-
end
-
-
# @method dump_failures
-
# @api public
-
#
-
# Dumps detailed information about each example failure.
-
#
-
# @param notification [NullNotification]
-
1
def dump_failures(notification)
-
1
return if notification.failure_notifications.empty?
-
output.puts notification.fully_formatted_failed_examples
-
end
-
-
# @method dump_summary
-
# @api public
-
#
-
# This method is invoked after the dumping of examples and failures. Each parameter
-
# is assigned to a corresponding attribute.
-
#
-
# @param summary [SummaryNotification] containing duration, example_count,
-
# failure_count and pending_count
-
1
def dump_summary(summary)
-
1
output.puts summary.fully_formatted
-
end
-
-
# @private
-
1
def dump_pending(notification)
-
1
return if notification.pending_examples.empty?
-
output.puts notification.fully_formatted_pending_examples
-
end
-
-
# @private
-
1
def seed(notification)
-
1
return unless notification.seed_used?
-
1
output.puts notification.fully_formatted
-
end
-
-
# @api public
-
#
-
# Invoked at the very end, `close` allows the formatter to clean
-
# up resources, e.g. open streams, etc.
-
#
-
# @param notification [NullNotification]
-
1
def close(_notification)
-
1
return unless IO === output
-
1
return if output.closed? || output == $stdout
-
-
output.close
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# ConsoleCodes provides helpers for formatting console output
-
# with ANSI codes, e.g. color's and bold.
-
1
module ConsoleCodes
-
# @private
-
1
VT100_CODES =
-
{
-
:black => 30,
-
:red => 31,
-
:green => 32,
-
:yellow => 33,
-
:blue => 34,
-
:magenta => 35,
-
:cyan => 36,
-
:white => 37,
-
:bold => 1,
-
}
-
# @private
-
1
VT100_CODE_VALUES = VT100_CODES.invert
-
-
1
module_function
-
-
# Fetches the correct code for the supplied symbol, or checks
-
# that a code is valid. Defaults to white (37).
-
#
-
# @param code_or_symbol [Symbol, Fixnum] Symbol or code to check
-
# @return [Fixnum] a console code
-
1
def console_code_for(code_or_symbol)
-
127
if RSpec.configuration.respond_to?(:"#{code_or_symbol}_color")
-
63
console_code_for configuration_color(code_or_symbol)
-
64
elsif VT100_CODE_VALUES.key?(code_or_symbol)
-
code_or_symbol
-
else
-
64
VT100_CODES.fetch(code_or_symbol) do
-
console_code_for(:white)
-
end
-
end
-
end
-
-
# Wraps a piece of text in ANSI codes with the supplied code. Will
-
# only apply the control code if `RSpec.configuration.color_enabled?`
-
# returns true.
-
#
-
# @param text [String] the text to wrap
-
# @param code_or_symbol [Symbol, Fixnum] the desired control code
-
# @return [String] the wrapped text
-
1
def wrap(text, code_or_symbol)
-
64
if RSpec.configuration.color_enabled?
-
64
"\e[#{console_code_for(code_or_symbol)}m#{text}\e[0m"
-
else
-
text
-
end
-
end
-
-
# @private
-
1
def configuration_color(code)
-
63
RSpec.configuration.__send__(:"#{code}_color")
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/base_text_formatter"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# @private
-
1
class ProgressFormatter < BaseTextFormatter
-
1
Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
-
-
1
def example_passed(_notification)
-
63
output.print ConsoleCodes.wrap('.', :success)
-
end
-
-
1
def example_pending(_notification)
-
output.print ConsoleCodes.wrap('*', :pending)
-
end
-
-
1
def example_failed(_notification)
-
output.print ConsoleCodes.wrap('F', :failure)
-
end
-
-
1
def start_dump(_notification)
-
1
output.puts
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/mocks'
-
-
1
module RSpec
-
1
module Core
-
1
module MockingAdapters
-
# @private
-
1
module RSpec
-
1
include ::RSpec::Mocks::ExampleMethods
-
-
1
def self.framework_name
-
1
:rspec
-
end
-
-
1
def self.configuration
-
::RSpec::Mocks.configuration
-
end
-
-
1
def setup_mocks_for_rspec
-
63
::RSpec::Mocks.setup
-
end
-
-
1
def verify_mocks_for_rspec
-
63
::RSpec::Mocks.verify
-
end
-
-
1
def teardown_mocks_for_rspec
-
63
::RSpec::Mocks.teardown
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "caller_filter"
-
1
RSpec::Support.require_rspec_support "warnings"
-
-
1
require 'rspec/matchers'
-
-
7
RSpec::Support.define_optimized_require_for_rspec(:expectations) { |f| require_relative(f) }
-
-
%w[
-
expectation_target
-
configuration
-
fail_with
-
handler
-
version
-
6
].each { |file| RSpec::Support.require_rspec_expectations(file) }
-
-
1
module RSpec
-
# RSpec::Expectations provides a simple, readable API to express
-
# the expected outcomes in a code example. To express an expected
-
# outcome, wrap an object or block in `expect`, call `to` or `to_not`
-
# (aliased as `not_to`) and pass it a matcher object:
-
#
-
# expect(order.total).to eq(Money.new(5.55, :USD))
-
# expect(list).to include(user)
-
# expect(message).not_to match(/foo/)
-
# expect { do_something }.to raise_error
-
#
-
# The last form (the block form) is needed to match against ruby constructs
-
# that are not objects, but can only be observed when executing a block
-
# of code. This includes raising errors, throwing symbols, yielding,
-
# and changing values.
-
#
-
# When `expect(...).to` is invoked with a matcher, it turns around
-
# and calls `matcher.matches?(<object wrapped by expect>)`. For example,
-
# in the expression:
-
#
-
# expect(order.total).to eq(Money.new(5.55, :USD))
-
#
-
# ...`eq(Money.new(5.55, :USD))` returns a matcher object, and it results
-
# in the equivalent of `eq.matches?(order.total)`. If `matches?` returns
-
# `true`, the expectation is met and execution continues. If `false`, then
-
# the spec fails with the message returned by `eq.failure_message`.
-
#
-
# Given the expression:
-
#
-
# expect(order.entries).not_to include(entry)
-
#
-
# ...the `not_to` method (also available as `to_not`) invokes the equivalent of
-
# `include.matches?(order.entries)`, but it interprets `false` as success, and
-
# `true` as a failure, using the message generated by
-
# `include.failure_message_when_negated`.
-
#
-
# rspec-expectations ships with a standard set of useful matchers, and writing
-
# your own matchers is quite simple.
-
#
-
# See [RSpec::Matchers](../RSpec/Matchers) for more information about the
-
# built-in matchers that ship with rspec-expectations, and how to write your
-
# own custom matchers.
-
1
module Expectations
-
# Exception raised when an expectation fails.
-
#
-
# @note We subclass Exception so that in a stub implementation if
-
# the user sets an expectation, it can't be caught in their
-
# code by a bare `rescue`.
-
# @api public
-
1
ExpectationNotMetError = Class.new(::Exception)
-
end
-
end
-
1
RSpec::Support.require_rspec_expectations "syntax"
-
-
1
module RSpec
-
1
module Expectations
-
# Provides configuration options for rspec-expectations.
-
# If you are using rspec-core, you can access this via a
-
# block passed to `RSpec::Core::Configuration#expect_with`.
-
# Otherwise, you can access it via RSpec::Expectations.configuration.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.expect_with :rspec do |c|
-
# # c is the config object
-
# end
-
# end
-
#
-
# # or
-
#
-
# RSpec::Expectations.configuration
-
1
class Configuration
-
# Configures the supported syntax.
-
# @param [Array<Symbol>, Symbol] values the syntaxes to enable
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.expect_with :rspec do |c|
-
# c.syntax = :should
-
# # or
-
# c.syntax = :expect
-
# # or
-
# c.syntax = [:should, :expect]
-
# end
-
# end
-
1
def syntax=(values)
-
2
if Array(values).include?(:expect)
-
2
Expectations::Syntax.enable_expect
-
else
-
Expectations::Syntax.disable_expect
-
end
-
-
2
if Array(values).include?(:should)
-
1
Expectations::Syntax.enable_should
-
else
-
1
Expectations::Syntax.disable_should
-
end
-
end
-
-
# The list of configured syntaxes.
-
# @return [Array<Symbol>] the list of configured syntaxes.
-
# @example
-
# unless RSpec::Matchers.configuration.syntax.include?(:expect)
-
# raise "this RSpec extension gem requires the rspec-expectations `:expect` syntax"
-
# end
-
1
def syntax
-
syntaxes = []
-
syntaxes << :should if Expectations::Syntax.should_enabled?
-
syntaxes << :expect if Expectations::Syntax.expect_enabled?
-
syntaxes
-
end
-
-
1
if ::RSpec.respond_to?(:configuration)
-
1
def color?
-
::RSpec.configuration.color_enabled?
-
end
-
else
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
attr_writer :color
-
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
def color?
-
@color
-
end
-
end
-
-
# Adds `should` and `should_not` to the given classes
-
# or modules. This can be used to ensure `should` works
-
# properly on things like proxy objects (particular
-
# `Delegator`-subclassed objects on 1.8).
-
#
-
# @param [Array<Module>] modules the list of classes or modules
-
# to add `should` and `should_not` to.
-
1
def add_should_and_should_not_to(*modules)
-
modules.each do |mod|
-
Expectations::Syntax.enable_should(mod)
-
end
-
end
-
-
# Sets or gets the backtrace formatter. The backtrace formatter should
-
# implement `#format_backtrace(Array<String>)`. This is used
-
# to format backtraces of errors handled by the `raise_error`
-
# matcher.
-
#
-
# If you are using rspec-core, rspec-core's backtrace formatting
-
# will be used (including respecting the presence or absence of
-
# the `--backtrace` option).
-
#
-
# @!attribute [rw] backtrace_formatter
-
1
attr_writer :backtrace_formatter
-
1
def backtrace_formatter
-
@backtrace_formatter ||= if defined?(::RSpec.configuration.backtrace_formatter)
-
::RSpec.configuration.backtrace_formatter
-
else
-
NullBacktraceFormatter
-
end
-
end
-
-
# Sets if custom matcher descriptions and failure messages
-
# should include clauses from methods defined using `chain`.
-
# @param value [Boolean]
-
1
attr_writer :include_chain_clauses_in_custom_matcher_descriptions
-
-
# Indicates whether or not custom matcher descriptions and failure messages
-
# should include clauses from methods defined using `chain`. It is
-
# false by default for backwards compatibility.
-
1
def include_chain_clauses_in_custom_matcher_descriptions?
-
@include_chain_clauses_in_custom_matcher_descriptions ||= false
-
end
-
-
# @private
-
1
def reset_syntaxes_to_default
-
1
self.syntax = [:should, :expect]
-
1
RSpec::Expectations::Syntax.warn_about_should!
-
end
-
-
# @api private
-
# Null implementation of a backtrace formatter used by default
-
# when rspec-core is not loaded. Does no filtering.
-
1
NullBacktraceFormatter = Module.new do
-
1
def self.format_backtrace(backtrace)
-
backtrace
-
end
-
end
-
end
-
-
# The configuration object.
-
# @return [RSpec::Expectations::Configuration] the configuration object
-
1
def self.configuration
-
2
@configuration ||= Configuration.new
-
end
-
-
# set default syntax
-
1
configuration.reset_syntaxes_to_default
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# Wraps the target of an expectation.
-
#
-
# @example
-
# expect(something) # => ExpectationTarget wrapping something
-
# expect { do_something } # => ExpectationTarget wrapping the block
-
#
-
# # used with `to`
-
# expect(actual).to eq(3)
-
#
-
# # with `not_to`
-
# expect(actual).not_to eq(3)
-
#
-
# @note `ExpectationTarget` is not intended to be instantiated
-
# directly by users. Use `expect` instead.
-
1
class ExpectationTarget
-
# @private
-
# Used as a sentinel value to be able to tell when the user
-
# did not pass an argument. We can't use `nil` for that because
-
# `nil` is a valid value to pass.
-
1
UndefinedValue = Module.new
-
-
# @api private
-
1
def initialize(value)
-
102
@target = value
-
end
-
-
# @private
-
1
def self.for(value, block)
-
102
if UndefinedValue.equal?(value)
-
18
unless block
-
raise ArgumentError, "You must pass either an argument or a block to `expect`."
-
end
-
18
BlockExpectationTarget.new(block)
-
84
elsif block
-
raise ArgumentError, "You cannot pass both an argument and a block to `expect`."
-
else
-
84
new(value)
-
end
-
end
-
-
# Runs the given expectation, passing if `matcher` returns true.
-
# @example
-
# expect(value).to eq(5)
-
# expect { perform }.to raise_error
-
# @param [Matcher]
-
# matcher
-
# @param [String or Proc] message optional message to display when the expectation fails
-
# @return [Boolean] true if the expectation succeeds (else raises)
-
# @see RSpec::Matchers
-
1
def to(matcher=nil, message=nil, &block)
-
101
prevent_operator_matchers(:to) unless matcher
-
101
RSpec::Expectations::PositiveExpectationHandler.handle_matcher(@target, matcher, message, &block)
-
end
-
-
# Runs the given expectation, passing if `matcher` returns false.
-
# @example
-
# expect(value).not_to eq(5)
-
# @param [Matcher]
-
# matcher
-
# @param [String or Proc] message optional message to display when the expectation fails
-
# @return [Boolean] false if the negative expectation succeeds (else raises)
-
# @see RSpec::Matchers
-
1
def not_to(matcher=nil, message=nil, &block)
-
1
prevent_operator_matchers(:not_to) unless matcher
-
1
RSpec::Expectations::NegativeExpectationHandler.handle_matcher(@target, matcher, message, &block)
-
end
-
1
alias to_not not_to
-
-
1
private
-
-
1
def prevent_operator_matchers(verb)
-
raise ArgumentError, "The expect syntax does not support operator matchers, " \
-
"so you must pass a matcher to `##{verb}`."
-
end
-
end
-
-
# @private
-
# Validates the provided matcher to ensure it supports block
-
# expectations, in order to avoid user confusion when they
-
# use a block thinking the expectation will be on the return
-
# value of the block rather than the block itself.
-
1
class BlockExpectationTarget < ExpectationTarget
-
1
def to(matcher, message=nil, &block)
-
17
enforce_block_expectation(matcher)
-
17
super
-
end
-
-
1
def not_to(matcher, message=nil, &block)
-
1
enforce_block_expectation(matcher)
-
1
super
-
end
-
1
alias to_not not_to
-
-
1
private
-
-
1
def enforce_block_expectation(matcher)
-
18
return if supports_block_expectations?(matcher)
-
-
raise ExpectationNotMetError, "You must pass an argument rather than " \
-
"a block to use the provided matcher (#{description_of matcher}), or " \
-
"the matcher must implement `supports_block_expectations?`."
-
end
-
-
1
def supports_block_expectations?(matcher)
-
18
matcher.supports_block_expectations?
-
rescue NoMethodError
-
false
-
end
-
-
1
def description_of(matcher)
-
matcher.description
-
rescue NoMethodError
-
matcher.inspect
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'differ'
-
-
1
module RSpec
-
1
module Expectations
-
1
class << self
-
# @private
-
1
def differ
-
RSpec::Support::Differ.new(
-
:object_preparer => lambda { |object| RSpec::Matchers::Composable.surface_descriptions_in(object) },
-
:color => RSpec::Matchers.configuration.color?
-
)
-
end
-
-
# Raises an RSpec::Expectations::ExpectationNotMetError with message.
-
# @param [String] message
-
# @param [Object] expected
-
# @param [Object] actual
-
#
-
# Adds a diff to the failure message when `expected` and `actual` are
-
# both present.
-
1
def fail_with(message, expected=nil, actual=nil)
-
unless message
-
raise ArgumentError, "Failure message is nil. Does your matcher define the " \
-
"appropriate failure_message[_when_negated] method to return a string?"
-
end
-
-
diff = differ.diff(actual, expected)
-
message = "#{message}\nDiff:#{diff}" unless diff.empty?
-
-
raise RSpec::Expectations::ExpectationNotMetError, message
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @private
-
1
module ExpectationHelper
-
1
def self.check_message(msg)
-
102
unless msg.nil? || msg.respond_to?(:to_str) || msg.respond_to?(:call)
-
::Kernel.warn [
-
"WARNING: ignoring the provided expectation message argument (",
-
msg.inspect,
-
") since it is not a string or a proc."
-
].join
-
end
-
end
-
-
# Returns an RSpec-3+ compatible matcher, wrapping a legacy one
-
# in an adapter if necessary.
-
#
-
# @private
-
1
def self.modern_matcher_from(matcher)
-
LegacyMatcherAdapter::RSpec2.wrap(matcher) ||
-
102
LegacyMatcherAdapter::RSpec1.wrap(matcher) || matcher
-
end
-
-
1
def self.setup(handler, matcher, message)
-
102
check_message(message)
-
102
::RSpec::Matchers.last_expectation_handler = handler
-
102
::RSpec::Matchers.last_matcher = modern_matcher_from(matcher)
-
end
-
-
1
def self.handle_failure(matcher, message, failure_message_method)
-
message = message.call if message.respond_to?(:call)
-
message ||= matcher.__send__(failure_message_method)
-
-
if matcher.respond_to?(:diffable?) && matcher.diffable?
-
::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
-
else
-
::RSpec::Expectations.fail_with message
-
end
-
end
-
end
-
-
# @private
-
1
class PositiveExpectationHandler
-
1
def self.handle_matcher(actual, initial_matcher, message=nil, &block)
-
101
matcher = ExpectationHelper.setup(self, initial_matcher, message)
-
-
101
return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher
-
101
matcher.matches?(actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message)
-
end
-
-
1
def self.verb
-
"should"
-
end
-
-
1
def self.should_method
-
:should
-
end
-
-
1
def self.opposite_should_method
-
:should_not
-
end
-
end
-
-
# @private
-
1
class NegativeExpectationHandler
-
1
def self.handle_matcher(actual, initial_matcher, message=nil, &block)
-
1
matcher = ExpectationHelper.setup(self, initial_matcher, message)
-
-
1
return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher
-
1
!(does_not_match?(matcher, actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message_when_negated))
-
end
-
-
1
def self.does_not_match?(matcher, actual, &block)
-
1
if matcher.respond_to?(:does_not_match?)
-
1
matcher.does_not_match?(actual, &block)
-
else
-
!matcher.matches?(actual, &block)
-
end
-
end
-
-
1
def self.verb
-
"should not"
-
end
-
-
1
def self.should_method
-
:should_not
-
end
-
-
1
def self.opposite_should_method
-
:should
-
end
-
end
-
-
# Wraps a matcher written against one of the legacy protocols in
-
# order to present the current protocol.
-
#
-
# @private
-
1
class LegacyMatcherAdapter < Matchers::MatcherDelegator
-
1
def initialize(matcher)
-
super
-
::RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''), :type => "legacy_matcher")
-
|#{matcher.class.name || matcher.inspect} implements a legacy RSpec matcher
-
|protocol. For the current protocol you should expose the failure messages
-
|via the `failure_message` and `failure_message_when_negated` methods.
-
|(Used from #{CallerFilter.first_non_rspec_line})
-
EOS
-
end
-
-
1
def self.wrap(matcher)
-
204
new(matcher) if interface_matches?(matcher)
-
end
-
-
# Starting in RSpec 1.2 (and continuing through all 2.x releases),
-
# the failure message protocol was:
-
# * `failure_message_for_should`
-
# * `failure_message_for_should_not`
-
# @private
-
1
class RSpec2 < self
-
1
def failure_message
-
base_matcher.failure_message_for_should
-
end
-
-
1
def failure_message_when_negated
-
base_matcher.failure_message_for_should_not
-
end
-
-
1
def self.interface_matches?(matcher)
-
(
-
!matcher.respond_to?(:failure_message) &&
-
102
matcher.respond_to?(:failure_message_for_should)
-
) || (
-
!matcher.respond_to?(:failure_message_when_negated) &&
-
102
matcher.respond_to?(:failure_message_for_should_not)
-
102
)
-
end
-
end
-
-
# Before RSpec 1.2, the failure message protocol was:
-
# * `failure_message`
-
# * `negative_failure_message`
-
# @private
-
1
class RSpec1 < self
-
1
def failure_message
-
base_matcher.failure_message
-
end
-
-
1
def failure_message_when_negated
-
base_matcher.negative_failure_message
-
end
-
-
# Note: `failure_message` is part of the RSpec 3 protocol
-
# (paired with `failure_message_when_negated`), so we don't check
-
# for `failure_message` here.
-
1
def self.interface_matches?(matcher)
-
!matcher.respond_to?(:failure_message_when_negated) &&
-
102
matcher.respond_to?(:negative_failure_message)
-
end
-
end
-
end
-
-
# RSpec 3.0 was released with the class name misspelled. For SemVer compatibility,
-
# we will provide this misspelled alias until 4.0.
-
# @deprecated Use LegacyMatcherAdapter instead.
-
# @private
-
1
LegacyMacherAdapter = LegacyMatcherAdapter
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @api private
-
# Provides methods for enabling and disabling the available
-
# syntaxes provided by rspec-expectations.
-
1
module Syntax
-
1
module_function
-
-
# @api private
-
# Determines where we add `should` and `should_not`.
-
1
def default_should_host
-
3
@default_should_host ||= ::Object.ancestors.last
-
end
-
-
# @api private
-
# Instructs rspec-expectations to warn on first usage of `should` or `should_not`.
-
# Enabled by default. This is largely here to facilitate testing.
-
1
def warn_about_should!
-
1
@warn_about_should = true
-
end
-
-
# @api private
-
# Generates a deprecation warning for the given method if no warning
-
# has already been issued.
-
1
def warn_about_should_unless_configured(method_name)
-
return unless @warn_about_should
-
-
RSpec.deprecate(
-
"Using `#{method_name}` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax",
-
:replacement => "the new `:expect` syntax or explicitly enable `:should`"
-
)
-
-
@warn_about_should = false
-
end
-
-
# @api private
-
# Enables the `should` syntax.
-
1
def enable_should(syntax_host=default_should_host)
-
1
@warn_about_should = false if syntax_host == default_should_host
-
1
return if should_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
def should(matcher=nil, message=nil, &block)
-
::RSpec::Expectations::Syntax.warn_about_should_unless_configured(__method__)
-
::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)
-
end
-
-
1
def should_not(matcher=nil, message=nil, &block)
-
::RSpec::Expectations::Syntax.warn_about_should_unless_configured(__method__)
-
::RSpec::Expectations::NegativeExpectationHandler.handle_matcher(self, matcher, message, &block)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the `should` syntax.
-
1
def disable_should(syntax_host=default_should_host)
-
1
return unless should_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
undef should
-
1
undef should_not
-
end
-
end
-
-
# @api private
-
# Enables the `expect` syntax.
-
1
def enable_expect(syntax_host=::RSpec::Matchers)
-
2
return if expect_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block)
-
102
::RSpec::Expectations::ExpectationTarget.for(value, block)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the `expect` syntax.
-
1
def disable_expect(syntax_host=::RSpec::Matchers)
-
return unless expect_enabled?(syntax_host)
-
-
syntax_host.module_exec do
-
undef expect
-
end
-
end
-
-
# @api private
-
# Indicates whether or not the `should` syntax is enabled.
-
1
def should_enabled?(syntax_host=default_should_host)
-
2
syntax_host.method_defined?(:should)
-
end
-
-
# @api private
-
# Indicates whether or not the `expect` syntax is enabled.
-
1
def expect_enabled?(syntax_host=::RSpec::Matchers)
-
2
syntax_host.method_defined?(:expect)
-
end
-
end
-
end
-
end
-
-
1
if defined?(BasicObject)
-
# The legacy `:should` syntax adds the following methods directly to
-
# `BasicObject` so that they are available off of any object. Note, however,
-
# that this syntax does not always play nice with delegate/proxy objects.
-
# We recommend you use the non-monkeypatching `:expect` syntax instead.
-
1
class BasicObject
-
# @method should
-
# Passes if `matcher` returns true. Available on every `Object`.
-
# @example
-
# actual.should eq expected
-
# actual.should match /expression/
-
# @param [Matcher]
-
# matcher
-
# @param [String] message optional message to display when the expectation fails
-
# @return [Boolean] true if the expectation succeeds (else raises)
-
# @note This is only available when you have enabled the `:should` syntax.
-
# @see RSpec::Matchers
-
-
# @method should_not
-
# Passes if `matcher` returns false. Available on every `Object`.
-
# @example
-
# actual.should_not eq expected
-
# @param [Matcher]
-
# matcher
-
# @param [String] message optional message to display when the expectation fails
-
# @return [Boolean] false if the negative expectation succeeds (else raises)
-
# @note This is only available when you have enabled the `:should` syntax.
-
# @see RSpec::Matchers
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @private
-
1
module Version
-
1
STRING = '3.1.2'
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support 'matcher_definition'
-
9
RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relative(f) }
-
-
%w[
-
pretty
-
composable
-
built_in
-
generated_descriptions
-
dsl
-
matcher_delegator
-
aliased_matcher
-
8
].each { |file| RSpec::Support.require_rspec_matchers(file) }
-
-
# RSpec's top level namespace. All of rspec-expectations is contained
-
# in the `RSpec::Expectations` and `RSpec::Matchers` namespaces.
-
1
module RSpec
-
# RSpec::Matchers provides a number of useful matchers we use to define
-
# expectations. Any object that implements the [matcher protocol](Matchers/MatcherProtocol)
-
# can be used as a matcher.
-
#
-
# ## Predicates
-
#
-
# In addition to matchers that are defined explicitly, RSpec will create
-
# custom matchers on the fly for any arbitrary predicate, giving your specs a
-
# much more natural language feel.
-
#
-
# A Ruby predicate is a method that ends with a "?" and returns true or false.
-
# Common examples are `empty?`, `nil?`, and `instance_of?`.
-
#
-
# All you need to do is write `expect(..).to be_` followed by the predicate
-
# without the question mark, and RSpec will figure it out from there.
-
# For example:
-
#
-
# expect([]).to be_empty # => [].empty?() | passes
-
# expect([]).not_to be_empty # => [].empty?() | fails
-
#
-
# In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_"
-
# and "be_an_", making your specs read much more naturally:
-
#
-
# expect("a string").to be_an_instance_of(String) # =>"a string".instance_of?(String) # passes
-
#
-
# expect(3).to be_a_kind_of(Fixnum) # => 3.kind_of?(Numeric) | passes
-
# expect(3).to be_a_kind_of(Numeric) # => 3.kind_of?(Numeric) | passes
-
# expect(3).to be_an_instance_of(Fixnum) # => 3.instance_of?(Fixnum) | passes
-
# expect(3).not_to be_an_instance_of(Numeric) # => 3.instance_of?(Numeric) | fails
-
#
-
# RSpec will also create custom matchers for predicates like `has_key?`. To
-
# use this feature, just state that the object should have_key(:key) and RSpec will
-
# call has_key?(:key) on the target. For example:
-
#
-
# expect(:a => "A").to have_key(:a)
-
# expect(:a => "A").to have_key(:b) # fails
-
#
-
# You can use this feature to invoke any predicate that begins with "has_", whether it is
-
# part of the Ruby libraries (like `Hash#has_key?`) or a method you wrote on your own class.
-
#
-
# Note that RSpec does not provide composable aliases for these dynamic predicate
-
# matchers. You can easily define your own aliases, though:
-
#
-
# RSpec::Matchers.alias_matcher :a_user_who_is_an_admin, :be_an_admin
-
# expect(user_list).to include(a_user_who_is_an_admin)
-
#
-
# ## Custom Matchers
-
#
-
# When you find that none of the stock matchers provide a natural feeling
-
# expectation, you can very easily write your own using RSpec's matcher DSL
-
# or writing one from scratch.
-
#
-
# ### Matcher DSL
-
#
-
# Imagine that you are writing a game in which players can be in various
-
# zones on a virtual board. To specify that bob should be in zone 4, you
-
# could say:
-
#
-
# expect(bob.current_zone).to eql(Zone.new("4"))
-
#
-
# But you might find it more expressive to say:
-
#
-
# expect(bob).to be_in_zone("4")
-
#
-
# and/or
-
#
-
# expect(bob).not_to be_in_zone("3")
-
#
-
# You can create such a matcher like so:
-
#
-
# RSpec::Matchers.define :be_in_zone do |zone|
-
# match do |player|
-
# player.in_zone?(zone)
-
# end
-
# end
-
#
-
# This will generate a <tt>be_in_zone</tt> method that returns a matcher
-
# with logical default messages for failures. You can override the failure
-
# messages and the generated description as follows:
-
#
-
# RSpec::Matchers.define :be_in_zone do |zone|
-
# match do |player|
-
# player.in_zone?(zone)
-
# end
-
#
-
# failure_message do |player|
-
# # generate and return the appropriate string.
-
# end
-
#
-
# failure_message_when_negated do |player|
-
# # generate and return the appropriate string.
-
# end
-
#
-
# description do
-
# # generate and return the appropriate string.
-
# end
-
# end
-
#
-
# Each of the message-generation methods has access to the block arguments
-
# passed to the <tt>create</tt> method (in this case, <tt>zone</tt>). The
-
# failure message methods (<tt>failure_message</tt> and
-
# <tt>failure_message_when_negated</tt>) are passed the actual value (the
-
# receiver of <tt>expect(..)</tt> or <tt>expect(..).not_to</tt>).
-
#
-
# ### Custom Matcher from scratch
-
#
-
# You could also write a custom matcher from scratch, as follows:
-
#
-
# class BeInZone
-
# def initialize(expected)
-
# @expected = expected
-
# end
-
#
-
# def matches?(target)
-
# @target = target
-
# @target.current_zone.eql?(Zone.new(@expected))
-
# end
-
#
-
# def failure_message
-
# "expected #{@target.inspect} to be in Zone #{@expected}"
-
# end
-
#
-
# def failure_message_when_negated
-
# "expected #{@target.inspect} not to be in Zone #{@expected}"
-
# end
-
# end
-
#
-
# ... and a method like this:
-
#
-
# def be_in_zone(expected)
-
# BeInZone.new(expected)
-
# end
-
#
-
# And then expose the method to your specs. This is normally done
-
# by including the method and the class in a module, which is then
-
# included in your spec:
-
#
-
# module CustomGameMatchers
-
# class BeInZone
-
# # ...
-
# end
-
#
-
# def be_in_zone(expected)
-
# # ...
-
# end
-
# end
-
#
-
# describe "Player behaviour" do
-
# include CustomGameMatchers
-
# # ...
-
# end
-
#
-
# or you can include in globally in a spec_helper.rb file <tt>require</tt>d
-
# from your spec file(s):
-
#
-
# RSpec::configure do |config|
-
# config.include(CustomGameMatchers)
-
# end
-
#
-
# ### Making custom matchers composable
-
#
-
# RSpec's built-in matchers are designed to be composed, in expressions like:
-
#
-
# expect(["barn", 2.45]).to contain_exactly(
-
# a_value_within(0.1).of(2.5),
-
# a_string_starting_with("bar")
-
# )
-
#
-
# Custom matchers can easily participate in composed matcher expressions like these.
-
# Include {RSpec::Matchers::Composable} in your custom matcher to make it support
-
# being composed (matchers defined using the DSL have this included automatically).
-
# Within your matcher's `matches?` method (or the `match` block, if using the DSL),
-
# use `values_match?(expected, actual)` rather than `expected == actual`.
-
# Under the covers, `values_match?` is able to match arbitrary
-
# nested data structures containing a mix of both matchers and non-matcher objects.
-
# It uses `===` and `==` to perform the matching, considering the values to
-
# match if either returns `true`. The `Composable` mixin also provides some helper
-
# methods for surfacing the matcher descriptions within your matcher's description
-
# or failure messages.
-
#
-
# RSpec's built-in matchers each have a number of aliases that rephrase the matcher
-
# from a verb phrase (such as `be_within`) to a noun phrase (such as `a_value_within`),
-
# which reads better when the matcher is passed as an argument in a composed matcher
-
# expressions, and also uses the noun-phrase wording in the matcher's `description`,
-
# for readable failure messages. You can alias your custom matchers in similar fashion
-
# using {RSpec::Matchers.alias_matcher}.
-
1
module Matchers
-
# @method expect
-
# Supports `expect(actual).to matcher` syntax by wrapping `actual` in an
-
# `ExpectationTarget`.
-
# @example
-
# expect(actual).to eq(expected)
-
# expect(actual).not_to eq(expected)
-
# @return [ExpectationTarget]
-
# @see ExpectationTarget#to
-
# @see ExpectationTarget#not_to
-
-
# Defines a matcher alias. The returned matcher's `description` will be overriden
-
# to reflect the phrasing of the new name, which will be used in failure messages
-
# when passed as an argument to another matcher in a composed matcher expression.
-
#
-
# @param new_name [Symbol] the new name for the matcher
-
# @param old_name [Symbol] the original name for the matcher
-
# @param options [Hash] options for the aliased matcher
-
# @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
-
# @yield [String] optional block that, when given is used to define the overriden
-
# description. The yielded arg is the original description. If no block is
-
# provided, a default description override is used based on the old and
-
# new names.
-
#
-
# @example
-
#
-
# RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
-
# sum_to(3).description # => "sum to 3"
-
# a_list_that_sums_to(3).description # => "a list that sums to 3"
-
#
-
# @example
-
#
-
# RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
-
# description.sub("be sorted by", "a list sorted by")
-
# end
-
#
-
# be_sorted_by(:age).description # => "be sorted by age"
-
# a_list_sorted_by(:age).description # => "a list sorted by age"
-
#
-
# @!macro [attach] alias_matcher
-
# @!parse
-
# alias $1 $2
-
1
def self.alias_matcher(new_name, old_name, options={}, &description_override)
-
description_override ||= lambda do |old_desc|
-
old_desc.gsub(Pretty.split_words(old_name), Pretty.split_words(new_name))
-
57
end
-
113
klass = options.fetch(:klass) { AliasedMatcher }
-
-
57
define_method(new_name) do |*args, &block|
-
matcher = __send__(old_name, *args, &block)
-
klass.new(matcher, description_override)
-
end
-
end
-
-
# Defines a negated matcher. The returned matcher's `description` and `failure_message`
-
# will be overriden to reflect the phrasing of the new name, and the match logic will
-
# be based on the original matcher but negated.
-
#
-
# @param negated_name [Symbol] the name for the negated matcher
-
# @param base_name [Symbol] the name of the original matcher that will be negated
-
# @yield [String] optional block that, when given is used to define the overriden
-
# description. The yielded arg is the original description. If no block is
-
# provided, a default description override is used based on the old and
-
# new names.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define_negated_matcher :a_value_not_between, :a_value_between
-
# a_value_between(3, 5).description # => "a value between 3 and 5"
-
# a_value_not_between(3, 5).description # => "a value not between 3 and 5"
-
1
def self.define_negated_matcher(negated_name, base_name, &description_override)
-
alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
-
end
-
-
# Passes if actual is truthy (anything but false or nil)
-
1
def be_truthy
-
BuiltIn::BeTruthy.new
-
end
-
1
alias_matcher :a_truthy_value, :be_truthy
-
-
# Passes if actual is falsey (false or nil)
-
1
def be_falsey
-
BuiltIn::BeFalsey.new
-
end
-
1
alias_matcher :be_falsy, :be_falsey
-
1
alias_matcher :a_falsey_value, :be_falsey
-
1
alias_matcher :a_falsy_value, :be_falsey
-
-
# Passes if actual is nil
-
1
def be_nil
-
3
BuiltIn::BeNil.new
-
end
-
1
alias_matcher :a_nil_value, :be_nil
-
-
# @example
-
# expect(actual).to be_truthy
-
# expect(actual).to be_falsey
-
# expect(actual).to be_nil
-
# expect(actual).to be_[arbitrary_predicate](*args)
-
# expect(actual).not_to be_nil
-
# expect(actual).not_to be_[arbitrary_predicate](*args)
-
#
-
# Given true, false, or nil, will pass if actual value is true, false or
-
# nil (respectively). Given no args means the caller should satisfy an if
-
# condition (to be or not to be).
-
#
-
# Predicates are any Ruby method that ends in a "?" and returns true or
-
# false. Given be_ followed by arbitrary_predicate (without the "?"),
-
# RSpec will match convert that into a query against the target object.
-
#
-
# The arbitrary_predicate feature will handle any predicate prefixed with
-
# "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) or "be_"
-
# (e.g. be_empty), letting you choose the prefix that best suits the
-
# predicate.
-
1
def be(*args)
-
11
args.empty? ? Matchers::BuiltIn::Be.new : equal(*args)
-
end
-
1
alias_matcher :a_value, :be, :klass => AliasedMatcherWithOperatorSupport
-
-
# passes if target.kind_of?(klass)
-
1
def be_a(klass)
-
22
be_a_kind_of(klass)
-
end
-
1
alias_method :be_an, :be_a
-
-
# Passes if actual.instance_of?(expected)
-
#
-
# @example
-
#
-
# expect(5).to be_an_instance_of(Fixnum)
-
# expect(5).not_to be_an_instance_of(Numeric)
-
# expect(5).not_to be_an_instance_of(Float)
-
1
def be_an_instance_of(expected)
-
BuiltIn::BeAnInstanceOf.new(expected)
-
end
-
1
alias_method :be_instance_of, :be_an_instance_of
-
1
alias_matcher :an_instance_of, :be_an_instance_of
-
-
# Passes if actual.kind_of?(expected)
-
#
-
# @example
-
#
-
# expect(5).to be_a_kind_of(Fixnum)
-
# expect(5).to be_a_kind_of(Numeric)
-
# expect(5).not_to be_a_kind_of(Float)
-
1
def be_a_kind_of(expected)
-
22
BuiltIn::BeAKindOf.new(expected)
-
end
-
1
alias_method :be_kind_of, :be_a_kind_of
-
1
alias_matcher :a_kind_of, :be_a_kind_of
-
-
# Passes if actual.between?(min, max). Works with any Comparable object,
-
# including String, Symbol, Time, or Numeric (Fixnum, Bignum, Integer,
-
# Float, Complex, and Rational).
-
#
-
# By default, `be_between` is inclusive (i.e. passes when given either the max or min value),
-
# but you can make it `exclusive` by chaining that off the matcher.
-
#
-
# @example
-
#
-
# expect(5).to be_between(1, 10)
-
# expect(11).not_to be_between(1, 10)
-
# expect(10).not_to be_between(1, 10).exclusive
-
1
def be_between(min, max)
-
2
BuiltIn::BeBetween.new(min, max)
-
end
-
1
alias_matcher :a_value_between, :be_between
-
-
# Passes if actual == expected +/- delta
-
#
-
# @example
-
#
-
# expect(result).to be_within(0.5).of(3.0)
-
# expect(result).not_to be_within(0.5).of(3.0)
-
1
def be_within(delta)
-
BuiltIn::BeWithin.new(delta)
-
end
-
1
alias_matcher :a_value_within, :be_within
-
1
alias_matcher :within, :be_within
-
-
# Applied to a proc, specifies that its execution will cause some value to
-
# change.
-
#
-
# @param [Object] receiver
-
# @param [Symbol] message the message to send the receiver
-
#
-
# You can either pass <tt>receiver</tt> and <tt>message</tt>, or a block,
-
# but not both.
-
#
-
# When passing a block, it must use the `{ ... }` format, not
-
# do/end, as `{ ... }` binds to the `change` method, whereas do/end
-
# would errantly bind to the `expect(..).to` or `expect(...).not_to` method.
-
#
-
# You can chain any of the following off of the end to specify details
-
# about the change:
-
#
-
# * `from`
-
# * `to`
-
#
-
# or any one of:
-
#
-
# * `by`
-
# * `by_at_least`
-
# * `by_at_most`
-
#
-
# @example
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by(1)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by_at_least(1)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by_at_most(1)
-
#
-
# string = "string"
-
# expect {
-
# string.reverse!
-
# }.to change { string }.from("string").to("gnirts")
-
#
-
# string = "string"
-
# expect {
-
# string
-
# }.not_to change { string }.from("string")
-
#
-
# expect {
-
# person.happy_birthday
-
# }.to change(person, :birthday).from(32).to(33)
-
#
-
# expect {
-
# employee.develop_great_new_social_networking_app
-
# }.to change(employee, :title).from("Mail Clerk").to("CEO")
-
#
-
# expect {
-
# doctor.leave_office
-
# }.to change(doctor, :sign).from(/is in/).to(/is out/)
-
#
-
# user = User.new(:type => "admin")
-
# expect {
-
# user.symbolize_type
-
# }.to change(user, :type).from(String).to(Symbol)
-
#
-
# == Notes
-
#
-
# Evaluates `receiver.message` or `block` before and after it
-
# evaluates the block passed to `expect`.
-
#
-
# `expect( ... ).not_to change` supports the form that specifies `from`
-
# (which specifies what you expect the starting, unchanged value to be)
-
# but does not support forms with subsequent calls to `by`, `by_at_least`,
-
# `by_at_most` or `to`.
-
1
def change(receiver=nil, message=nil, &block)
-
BuiltIn::Change.new(receiver, message, &block)
-
end
-
1
alias_matcher :a_block_changing, :change
-
1
alias_matcher :changing, :change
-
-
# Passes if actual contains all of the expected regardless of order.
-
# This works for collections. Pass in multiple args and it will only
-
# pass if all args are found in collection.
-
#
-
# @note This is also available using the `=~` operator with `should`,
-
# but `=~` is not supported with `expect`.
-
#
-
# @example
-
#
-
# expect([1, 2, 3]).to contain_exactly(1, 2, 3)
-
# expect([1, 2, 3]).to contain_exactly(1, 3, 2)
-
#
-
# @see #match_array
-
1
def contain_exactly(*items)
-
BuiltIn::ContainExactly.new(items)
-
end
-
1
alias_matcher :a_collection_containing_exactly, :contain_exactly
-
1
alias_matcher :containing_exactly, :contain_exactly
-
-
# Passes if actual covers expected. This works for
-
# Ranges. You can also pass in multiple args
-
# and it will only pass if all args are found in Range.
-
#
-
# @example
-
# expect(1..10).to cover(5)
-
# expect(1..10).to cover(4, 6)
-
# expect(1..10).to cover(4, 6, 11) # fails
-
# expect(1..10).not_to cover(11)
-
# expect(1..10).not_to cover(5) # fails
-
#
-
# ### Warning:: Ruby >= 1.9 only
-
1
def cover(*values)
-
BuiltIn::Cover.new(*values)
-
end
-
1
alias_matcher :a_range_covering, :cover
-
1
alias_matcher :covering, :cover
-
-
# Matches if the actual value ends with the expected value(s). In the case
-
# of a string, matches against the last `expected.length` characters of the
-
# actual string. In the case of an array, matches against the last
-
# `expected.length` elements of the actual array.
-
#
-
# @example
-
#
-
# expect("this string").to end_with "string"
-
# expect([0, 1, 2, 3, 4]).to end_with 4
-
# expect([0, 2, 3, 4, 4]).to end_with 3, 4
-
1
def end_with(*expected)
-
BuiltIn::EndWith.new(*expected)
-
end
-
1
alias_matcher :a_collection_ending_with, :end_with
-
1
alias_matcher :a_string_ending_with, :end_with
-
1
alias_matcher :ending_with, :end_with
-
-
# Passes if <tt>actual == expected</tt>.
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
#
-
# expect(5).to eq(5)
-
# expect(5).not_to eq(3)
-
1
def eq(expected)
-
38
BuiltIn::Eq.new(expected)
-
end
-
1
alias_matcher :an_object_eq_to, :eq
-
1
alias_matcher :eq_to, :eq
-
-
# Passes if `actual.eql?(expected)`
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
#
-
# expect(5).to eql(5)
-
# expect(5).not_to eql(3)
-
1
def eql(expected)
-
2
BuiltIn::Eql.new(expected)
-
end
-
1
alias_matcher :an_object_eql_to, :eql
-
1
alias_matcher :eql_to, :eql
-
-
# Passes if <tt>actual.equal?(expected)</tt> (object identity).
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
#
-
# expect(5).to equal(5) # Fixnums are equal
-
# expect("5").not_to equal("5") # Strings that look the same are not the same object
-
1
def equal(expected)
-
11
BuiltIn::Equal.new(expected)
-
end
-
1
alias_matcher :an_object_equal_to, :equal
-
1
alias_matcher :equal_to, :equal
-
-
# Passes if `actual.exist?` or `actual.exists?`
-
#
-
# @example
-
# expect(File).to exist("path/to/file")
-
1
def exist(*args)
-
BuiltIn::Exist.new(*args)
-
end
-
1
alias_matcher :an_object_existing, :exist
-
1
alias_matcher :existing, :exist
-
-
# Passes if actual's attribute values match the expected attributes hash.
-
# This works no matter how you define your attribute readers.
-
#
-
# @example
-
#
-
# Person = Struct.new(:name, :age)
-
# person = Person.new("Bob", 32)
-
#
-
# expect(person).to have_attributes(:name => "Bob", :age => 32)
-
# expect(person).to have_attributes(:name => a_string_starting_with("B"), :age => (a_value > 30) )
-
#
-
# @note It will fail if actual doesn't respond to any of the expected attributes.
-
#
-
# @example
-
#
-
# expect(person).to have_attributes(:color => "red")
-
1
def have_attributes(expected)
-
BuiltIn::HaveAttributes.new(expected)
-
end
-
1
alias_matcher :an_object_having_attributes, :have_attributes
-
-
# Passes if actual includes expected. This works for
-
# collections and Strings. You can also pass in multiple args
-
# and it will only pass if all args are found in collection.
-
#
-
# @example
-
#
-
# expect([1,2,3]).to include(3)
-
# expect([1,2,3]).to include(2,3)
-
# expect([1,2,3]).to include(2,3,4) # fails
-
# expect([1,2,3]).not_to include(4)
-
# expect("spread").to include("read")
-
# expect("spread").not_to include("red")
-
1
def include(*expected)
-
BuiltIn::Include.new(*expected)
-
end
-
1
alias_matcher :a_collection_including, :include
-
1
alias_matcher :a_string_including, :include
-
1
alias_matcher :a_hash_including, :include
-
1
alias_matcher :including, :include
-
-
# Passes if actual all expected objects pass. This works for
-
# any enumerable object.
-
#
-
# @example
-
#
-
# expect([1, 3, 5]).to all be_odd
-
# expect([1, 3, 6]).to all be_odd # fails
-
#
-
# @note The negative form `not_to all` is not supported. Instead
-
# use `not_to include` or pass a negative form of a matcher
-
# as the argument (e.g. `all exclude(:foo)`).
-
#
-
# @note You can also use this with compound matchers as well.
-
#
-
# @example
-
# expect([1, 3, 5]).to all( be_odd.and be_an(Integer) )
-
1
def all(expected)
-
BuiltIn::All.new(expected)
-
end
-
-
# Given a `Regexp` or `String`, passes if `actual.match(pattern)`
-
# Given an arbitrary nested data structure (e.g. arrays and hashes),
-
# matches if `expected === actual` || `actual == expected` for each
-
# pair of elements.
-
#
-
# @example
-
#
-
# expect(email).to match(/^([^\s]+)((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
-
# expect(email).to match("@example.com")
-
#
-
# @example
-
#
-
# hash = {
-
# :a => {
-
# :b => ["foo", 5],
-
# :c => { :d => 2.05 }
-
# }
-
# }
-
#
-
# expect(hash).to match(
-
# :a => {
-
# :b => a_collection_containing_exactly(
-
# a_string_starting_with("f"),
-
# an_instance_of(Fixnum)
-
# ),
-
# :c => { :d => (a_value < 3) }
-
# }
-
# )
-
#
-
# @note The `match_regex` alias is deprecated and is not recommended for use.
-
# It was added in 2.12.1 to facilitate its use from within custom
-
# matchers (due to how the custom matcher DSL was evaluated in 2.x,
-
# `match` could not be used there), but is no longer needed in 3.x.
-
1
def match(expected)
-
BuiltIn::Match.new(expected)
-
end
-
1
alias_matcher :match_regex, :match
-
1
alias_matcher :an_object_matching, :match
-
1
alias_matcher :a_string_matching, :match
-
1
alias_matcher :matching, :match
-
-
# An alternate form of `contain_exactly` that accepts
-
# the expected contents as a single array arg rather
-
# that splatted out as individual items.
-
#
-
# @example
-
#
-
# expect(results).to contain_exactly(1, 2)
-
# # is identical to:
-
# expect(results).to match_array([1, 2])
-
#
-
# @see #contain_exactly
-
1
def match_array(items)
-
contain_exactly(*items)
-
end
-
-
# With no arg, passes if the block outputs `to_stdout` or `to_stderr`.
-
# With a string, passes if the blocks outputs that specific string `to_stdout` or `to_stderr`.
-
# With a regexp or matcher, passes if the blocks outputs a string `to_stdout` or `to_stderr` that matches.
-
#
-
# @example
-
#
-
# expect { print 'foo' }.to output.to_stdout
-
# expect { print 'foo' }.to output('foo').to_stdout
-
# expect { print 'foo' }.to output(/foo/).to_stdout
-
#
-
# expect { do_something }.to_not output.to_stdout
-
#
-
# expect { warn('foo') }.to output.to_stderr
-
# expect { warn('foo') }.to output('foo').to_stderr
-
# expect { warn('foo') }.to output(/foo/).to_stderr
-
#
-
# expect { do_something }.to_not output.to_stderr
-
#
-
# @note This matcher works by temporarily replacing `$stdout` or `$stderr`,
-
# so it's not able to intercept stream output that explicitly uses `STDOUT`/`STDERR`
-
# or that uses a reference to `$stdout`/`$stderr` that was stored before the
-
# matcher is used.
-
1
def output(expected=nil)
-
BuiltIn::Output.new(expected)
-
end
-
1
alias_matcher :a_block_outputting, :output
-
-
# With no args, matches if any error is raised.
-
# With a named error, matches only if that specific error is raised.
-
# With a named error and messsage specified as a String, matches only if both match.
-
# With a named error and messsage specified as a Regexp, matches only if both match.
-
# Pass an optional block to perform extra verifications on the exception matched
-
#
-
# @example
-
#
-
# expect { do_something_risky }.to raise_error
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError)
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError) { |error| expect(error.data).to eq 42 }
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError, "that was too risky")
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError, /oo ri/)
-
#
-
# expect { do_something_risky }.not_to raise_error
-
1
def raise_error(error=Exception, message=nil, &block)
-
18
BuiltIn::RaiseError.new(error, message, &block)
-
end
-
1
alias_method :raise_exception, :raise_error
-
-
1
alias_matcher :a_block_raising, :raise_error do |desc|
-
desc.sub("raise", "a block raising")
-
end
-
-
1
alias_matcher :raising, :raise_error do |desc|
-
desc.sub("raise", "raising")
-
end
-
-
# Matches if the target object responds to all of the names
-
# provided. Names can be Strings or Symbols.
-
#
-
# @example
-
#
-
# expect("string").to respond_to(:length)
-
#
-
1
def respond_to(*names)
-
1
BuiltIn::RespondTo.new(*names)
-
end
-
1
alias_matcher :an_object_responding_to, :respond_to
-
1
alias_matcher :responding_to, :respond_to
-
-
# Passes if the submitted block returns true. Yields target to the
-
# block.
-
#
-
# Generally speaking, this should be thought of as a last resort when
-
# you can't find any other way to specify the behaviour you wish to
-
# specify.
-
#
-
# If you do find yourself in such a situation, you could always write
-
# a custom matcher, which would likely make your specs more expressive.
-
#
-
# @example
-
#
-
# expect(5).to satisfy { |n| n > 3 }
-
1
def satisfy(&block)
-
BuiltIn::Satisfy.new(&block)
-
end
-
1
alias_matcher :an_object_satisfying, :satisfy
-
1
alias_matcher :satisfying, :satisfy
-
-
# Matches if the actual value starts with the expected value(s). In the
-
# case of a string, matches against the first `expected.length` characters
-
# of the actual string. In the case of an array, matches against the first
-
# `expected.length` elements of the actual array.
-
#
-
# @example
-
#
-
# expect("this string").to start_with "this s"
-
# expect([0, 1, 2, 3, 4]).to start_with 0
-
# expect([0, 2, 3, 4, 4]).to start_with 0, 1
-
1
def start_with(*expected)
-
BuiltIn::StartWith.new(*expected)
-
end
-
1
alias_matcher :a_collection_starting_with, :start_with
-
1
alias_matcher :a_string_starting_with, :start_with
-
1
alias_matcher :starting_with, :start_with
-
-
# Given no argument, matches if a proc throws any Symbol.
-
#
-
# Given a Symbol, matches if the given proc throws the specified Symbol.
-
#
-
# Given a Symbol and an arg, matches if the given proc throws the
-
# specified Symbol with the specified arg.
-
#
-
# @example
-
#
-
# expect { do_something_risky }.to throw_symbol
-
# expect { do_something_risky }.to throw_symbol(:that_was_risky)
-
# expect { do_something_risky }.to throw_symbol(:that_was_risky, 'culprit')
-
#
-
# expect { do_something_risky }.not_to throw_symbol
-
# expect { do_something_risky }.not_to throw_symbol(:that_was_risky)
-
# expect { do_something_risky }.not_to throw_symbol(:that_was_risky, 'culprit')
-
1
def throw_symbol(expected_symbol=nil, expected_arg=nil)
-
BuiltIn::ThrowSymbol.new(expected_symbol, expected_arg)
-
end
-
-
1
alias_matcher :a_block_throwing, :throw_symbol do |desc|
-
desc.sub("throw", "a block throwing")
-
end
-
-
1
alias_matcher :throwing, :throw_symbol do |desc|
-
desc.sub("throw", "throwing")
-
end
-
-
# Passes if the method called in the expect block yields, regardless
-
# of whether or not arguments are yielded.
-
#
-
# @example
-
#
-
# expect { |b| 5.tap(&b) }.to yield_control
-
# expect { |b| "a".to_sym(&b) }.not_to yield_control
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_control
-
BuiltIn::YieldControl.new
-
end
-
1
alias_matcher :a_block_yielding_control, :yield_control
-
1
alias_matcher :yielding_control, :yield_control
-
-
# Passes if the method called in the expect block yields with
-
# no arguments. Fails if it does not yield, or yields with arguments.
-
#
-
# @example
-
#
-
# expect { |b| User.transaction(&b) }.to yield_with_no_args
-
# expect { |b| 5.tap(&b) }.not_to yield_with_no_args # because it yields with `5`
-
# expect { |b| "a".to_sym(&b) }.not_to yield_with_no_args # because it does not yield
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_with_no_args
-
BuiltIn::YieldWithNoArgs.new
-
end
-
1
alias_matcher :a_block_yielding_with_no_args, :yield_with_no_args
-
1
alias_matcher :yielding_with_no_args, :yield_with_no_args
-
-
# Given no arguments, matches if the method called in the expect
-
# block yields with arguments (regardless of what they are or how
-
# many there are).
-
#
-
# Given arguments, matches if the method called in the expect block
-
# yields with arguments that match the given arguments.
-
#
-
# Argument matching is done using `===` (the case match operator)
-
# and `==`. If the expected and actual arguments match with either
-
# operator, the matcher will pass.
-
#
-
# @example
-
#
-
# expect { |b| 5.tap(&b) }.to yield_with_args # because #tap yields an arg
-
# expect { |b| 5.tap(&b) }.to yield_with_args(5) # because 5 == 5
-
# expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum) # because Fixnum === 5
-
# expect { |b| File.open("f.txt", &b) }.to yield_with_args(/txt/) # because /txt/ === "f.txt"
-
#
-
# expect { |b| User.transaction(&b) }.not_to yield_with_args # because it yields no args
-
# expect { |b| 5.tap(&b) }.not_to yield_with_args(1, 2, 3)
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_with_args(*args)
-
BuiltIn::YieldWithArgs.new(*args)
-
end
-
1
alias_matcher :a_block_yielding_with_args, :yield_with_args
-
1
alias_matcher :yielding_with_args, :yield_with_args
-
-
# Designed for use with methods that repeatedly yield (such as
-
# iterators). Passes if the method called in the expect block yields
-
# multiple times with arguments matching those given.
-
#
-
# Argument matching is done using `===` (the case match operator)
-
# and `==`. If the expected and actual arguments match with either
-
# operator, the matcher will pass.
-
#
-
# @example
-
#
-
# expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
-
# expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
-
# expect { |b| [1, 2, 3].each(&b) }.not_to yield_successive_args(1, 2)
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
1
def yield_successive_args(*args)
-
BuiltIn::YieldSuccessiveArgs.new(*args)
-
end
-
1
alias_matcher :a_block_yielding_successive_args, :yield_successive_args
-
1
alias_matcher :yielding_successive_args, :yield_successive_args
-
-
# Delegates to {RSpec::Expectations.configuration}.
-
# This is here because rspec-core's `expect_with` option
-
# looks for a `configuration` method on the mixin
-
# (`RSpec::Matchers`) to yield to a block.
-
# @return [RSpec::Expectations::Configuration] the configuration object
-
1
def self.configuration
-
1
Expectations.configuration
-
end
-
-
1
private
-
-
1
BE_PREDICATE_REGEX = /^(be_(?:an?_)?)(.*)/
-
1
HAS_REGEX = /^(?:have_)(.*)/
-
-
1
def method_missing(method, *args, &block)
-
2
case method.to_s
-
when BE_PREDICATE_REGEX
-
2
BuiltIn::BePredicate.new(method, *args, &block)
-
when HAS_REGEX
-
BuiltIn::Has.new(method, *args, &block)
-
else
-
super
-
end
-
end
-
-
# @api private
-
1
def self.is_a_matcher?(obj)
-
17
return true if ::RSpec::Matchers::BuiltIn::BaseMatcher === obj
-
17
begin
-
17
return false if obj.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher)
-
rescue NoMethodError
-
# Some objects, like BasicObject, don't implemented standard
-
# reflection methods.
-
return false
-
end
-
17
return false unless obj.respond_to?(:matches?)
-
-
obj.respond_to?(:failure_message) ||
-
obj.respond_to?(:failure_message_for_should) # support legacy matchers
-
end
-
-
1
::RSpec::Support.register_matcher_definition do |obj|
-
is_a_matcher?(obj)
-
end
-
-
# @api private
-
1
def self.is_a_describable_matcher?(obj)
-
is_a_matcher?(obj) && obj.respond_to?(:description)
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Decorator that wraps a matcher and overrides `description`
-
# using the provided block in order to support an alias
-
# of a matcher. This is intended for use when composing
-
# matchers, so that you can use an expression like
-
# `include( a_value_within(0.1).of(3) )` rather than
-
# `include( be_within(0.1).of(3) )`, and have the corresponding
-
# description read naturally.
-
#
-
# @api private
-
1
class AliasedMatcher < MatcherDelegator
-
1
def initialize(base_matcher, description_block)
-
@description_block = description_block
-
super(base_matcher)
-
end
-
-
# Forward messages on to the wrapped matcher.
-
# Since many matchers provide a fluent interface
-
# (e.g. `a_value_within(0.1).of(3)`), we need to wrap
-
# the returned value if it responds to `description`,
-
# so that our override can be applied when it is eventually
-
# used.
-
1
def method_missing(*)
-
return_val = super
-
return return_val unless RSpec::Matchers.is_a_matcher?(return_val)
-
self.class.new(return_val, @description_block)
-
end
-
-
# Provides the description of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The description is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def description
-
@description_block.call(super)
-
end
-
-
# Provides the failure_message of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The failure_message is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def failure_message
-
@description_block.call(super)
-
end
-
-
# Provides the failure_message_when_negated of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The failure_message_when_negated is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def failure_message_when_negated
-
@description_block.call(super)
-
end
-
end
-
-
# Decorator used for matchers that have special implementations of
-
# operators like `==` and `===`.
-
# @private
-
1
class AliasedMatcherWithOperatorSupport < AliasedMatcher
-
# We undef these so that they get delegated via `method_missing`.
-
1
undef ==
-
1
undef ===
-
end
-
-
# @private
-
1
class AliasedNegatedMatcher < AliasedMatcher
-
1
def matches?(*args, &block)
-
if @base_matcher.respond_to?(:does_not_match?)
-
@base_matcher.does_not_match?(*args, &block)
-
else
-
!super
-
end
-
end
-
-
1
def does_not_match?(*args, &block)
-
@base_matcher.matches?(*args, &block)
-
end
-
-
1
def failure_message
-
optimal_failure_message(__method__, :failure_message_when_negated)
-
end
-
-
1
def failure_message_when_negated
-
optimal_failure_message(__method__, :failure_message)
-
end
-
-
1
private
-
-
1
DefaultFailureMessages = BuiltIn::BaseMatcher::DefaultFailureMessages
-
-
# For a matcher that uses the default failure messages, we prefer to
-
# use the override provided by the `description_block`, because it
-
# includes the phrasing that the user has expressed a preference for
-
# by going through the effort of defining a negated matcher.
-
#
-
# However, if the override didn't actually change anything, then we
-
# should return the opposite failure message instead -- the overriden
-
# message is going to be confusing if we return it as-is, as it represents
-
# the non-negated failure message for a negated match (or vice versa).
-
1
def optimal_failure_message(same, inverted)
-
if DefaultFailureMessages.has_default_failure_messages?(@base_matcher)
-
base_message = @base_matcher.__send__(same)
-
overriden = @description_block.call(base_message)
-
return overriden if overriden != base_message
-
end
-
-
@base_matcher.__send__(inverted)
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_matchers "built_in/base_matcher"
-
-
1
module RSpec
-
1
module Matchers
-
# Container module for all built-in matchers. The matcher classes are here
-
# (rather than directly under `RSpec::Matchers`) in order to prevent name
-
# collisions, since `RSpec::Matchers` gets included into the user's namespace.
-
#
-
# Autoloading is used to delay when the matcher classes get loaded, allowing
-
# rspec-matchers to boot faster, and avoiding loading matchers the user is
-
# not using.
-
1
module BuiltIn
-
1
autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of'
-
1
autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of'
-
1
autoload :BeBetween, 'rspec/matchers/built_in/be_between'
-
1
autoload :Be, 'rspec/matchers/built_in/be'
-
1
autoload :BeComparedTo, 'rspec/matchers/built_in/be'
-
1
autoload :BeFalsey, 'rspec/matchers/built_in/be'
-
1
autoload :BeNil, 'rspec/matchers/built_in/be'
-
1
autoload :BePredicate, 'rspec/matchers/built_in/be'
-
1
autoload :BeTruthy, 'rspec/matchers/built_in/be'
-
1
autoload :BeWithin, 'rspec/matchers/built_in/be_within'
-
1
autoload :Change, 'rspec/matchers/built_in/change'
-
1
autoload :Compound, 'rspec/matchers/built_in/compound'
-
1
autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly'
-
1
autoload :Cover, 'rspec/matchers/built_in/cover'
-
1
autoload :EndWith, 'rspec/matchers/built_in/start_and_end_with'
-
1
autoload :Eq, 'rspec/matchers/built_in/eq'
-
1
autoload :Eql, 'rspec/matchers/built_in/eql'
-
1
autoload :Equal, 'rspec/matchers/built_in/equal'
-
1
autoload :Exist, 'rspec/matchers/built_in/exist'
-
1
autoload :Has, 'rspec/matchers/built_in/has'
-
1
autoload :HaveAttributes, 'rspec/matchers/built_in/have_attributes'
-
1
autoload :Include, 'rspec/matchers/built_in/include'
-
1
autoload :All, 'rspec/matchers/built_in/all'
-
1
autoload :Match, 'rspec/matchers/built_in/match'
-
1
autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :OperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :Output, 'rspec/matchers/built_in/output'
-
1
autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :RaiseError, 'rspec/matchers/built_in/raise_error'
-
1
autoload :RespondTo, 'rspec/matchers/built_in/respond_to'
-
1
autoload :Satisfy, 'rspec/matchers/built_in/satisfy'
-
1
autoload :StartWith, 'rspec/matchers/built_in/start_and_end_with'
-
1
autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
-
1
autoload :YieldControl, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldWithArgs, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield'
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
#
-
# Used _internally_ as a base class for matchers that ship with
-
# rspec-expectations and rspec-rails.
-
#
-
# ### Warning:
-
#
-
# This class is for internal use, and subject to change without notice. We
-
# strongly recommend that you do not base your custom matchers on this
-
# class. If/when this changes, we will announce it and remove this warning.
-
1
class BaseMatcher
-
1
include RSpec::Matchers::Pretty
-
1
include RSpec::Matchers::Composable
-
-
# @api private
-
# Used to detect when no arg is passed to `initialize`.
-
# `nil` cannot be used because it's a valid value to pass.
-
1
UNDEFINED = Object.new.freeze
-
-
# @private
-
1
attr_reader :actual, :expected, :rescued_exception
-
-
1
def initialize(expected=UNDEFINED)
-
76
@expected = expected unless UNDEFINED.equal?(expected)
-
end
-
-
# @api private
-
# Indicates if the match is successful. Delegates to `match`, which
-
# should be defined on a subclass. Takes care of consistently
-
# initializing the `actual` attribute.
-
1
def matches?(actual)
-
76
@actual = actual
-
76
match(expected, actual)
-
end
-
-
# @api private
-
# Used to wrap a block of code that will indicate failure by
-
# raising one of the named exceptions.
-
#
-
# This is used by rspec-rails for some of its matchers that
-
# wrap rails' assertions.
-
1
def match_unless_raises(*exceptions)
-
exceptions.unshift Exception if exceptions.empty?
-
begin
-
yield
-
true
-
rescue *exceptions => @rescued_exception
-
false
-
end
-
end
-
-
# @api private
-
# Generates a "pretty" description using the logic in {Pretty}.
-
# @return [String]
-
1
def description
-
return name_to_sentence unless defined?(@expected)
-
"#{name_to_sentence}#{to_sentence @expected}"
-
end
-
-
# @api private
-
# Matchers are not diffable by default. Override this to make your
-
# subclass diffable.
-
1
def diffable?
-
false
-
end
-
-
# @api private
-
# Most matchers are value matchers (i.e. meant to work with `expect(value)`)
-
# rather than block matchers (i.e. meant to work with `expect { }`), so
-
# this defaults to false. Block matchers must override this to return true.
-
1
def supports_block_expectations?
-
false
-
end
-
-
# @api private
-
1
def expects_call_stack_jump?
-
false
-
end
-
-
1
private
-
-
1
def assert_ivars(*expected_ivars)
-
return unless (expected_ivars - present_ivars).any?
-
raise "#{self.class.name} needs to supply#{to_sentence expected_ivars}"
-
end
-
-
1
if RUBY_VERSION.to_f < 1.9
-
def present_ivars
-
instance_variables.map { |v| v.to_sym }
-
end
-
else
-
1
alias present_ivars instance_variables
-
end
-
-
# @api private
-
# Provides default implementations of failure messages, based on the `description`.
-
1
module DefaultFailureMessages
-
# @api private
-
# Provides a good generic failure message. Based on `description`.
-
# When subclassing, if you are not satisfied with this failure message
-
# you often only need to override `description`.
-
# @return [String]
-
1
def failure_message
-
"expected #{actual.inspect} to #{description}"
-
end
-
-
# @api private
-
# Provides a good generic negative failure message. Based on `description`.
-
# When subclassing, if you are not satisfied with this failure message
-
# you often only need to override `description`.
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{actual.inspect} not to #{description}"
-
end
-
-
# @private
-
1
def self.has_default_failure_messages?(matcher)
-
matcher.method(:failure_message).owner == self &&
-
matcher.method(:failure_message_when_negated).owner == self
-
rescue NameError
-
false
-
end
-
end
-
-
1
include DefaultFailureMessages
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `be_truthy`.
-
# Not intended to be instantiated directly.
-
1
class BeTruthy < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: truthy value\n got: #{actual.inspect}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: falsey value\n got: #{actual.inspect}"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be_falsey`.
-
# Not intended to be instantiated directly.
-
1
class BeFalsey < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: falsey value\n got: #{actual.inspect}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: truthy value\n got: #{actual.inspect}"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be_nil`.
-
# Not intended to be instantiated directly.
-
1
class BeNil < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: nil\n got: #{actual.inspect}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: not nil\n got: nil"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
3
actual.nil?
-
end
-
end
-
-
# @private
-
1
module BeHelpers
-
1
private
-
-
1
def args_to_s
-
@args.empty? ? "" : parenthesize(inspected_args.join(', '))
-
end
-
-
1
def parenthesize(string)
-
"(#{string})"
-
end
-
-
1
def inspected_args
-
@args.map { |a| a.inspect }
-
end
-
-
1
def expected_to_sentence
-
split_words(@expected)
-
end
-
-
1
def args_to_sentence
-
to_sentence(@args)
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be`.
-
# Not intended to be instantiated directly.
-
1
class Be < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(*args)
-
@args = args
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected #{@actual.inspect} to evaluate to true"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{@actual.inspect} to evaluate to false"
-
end
-
-
1
[:==, :<, :<=, :>=, :>, :===, :=~].each do |operator|
-
7
define_method operator do |operand|
-
BeComparedTo.new(operand, operator)
-
end
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation of `be <operator> value`.
-
# Not intended to be instantiated directly.
-
1
class BeComparedTo < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(operand, operator)
-
@expected, @operator = operand, operator
-
@args = []
-
end
-
-
1
def matches?(actual)
-
@actual = actual
-
@actual.__send__ @operator, @expected
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: #{@operator} #{@expected.inspect}\n got: #{@operator.to_s.gsub(/./, ' ')} #{@actual.inspect}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
message = "`expect(#{@actual.inspect}).not_to be #{@operator} #{@expected.inspect}`"
-
if [:<, :>, :<=, :>=].include?(@operator)
-
message + " not only FAILED, it is a bit confusing."
-
else
-
message
-
end
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
-
end
-
end
-
-
# @api private
-
# Provides the implementation of `be_<predicate>`.
-
# Not intended to be instantiated directly.
-
1
class BePredicate < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(*args, &block)
-
2
@expected = parse_expected(args.shift)
-
2
@args = args
-
2
@block = block
-
end
-
-
1
def matches?(actual, &block)
-
2
@actual = actual
-
2
@block ||= block
-
2
predicate_accessible? && predicate_matches?
-
end
-
-
1
def does_not_match?(actual, &block)
-
@actual = actual
-
@block ||= block
-
predicate_accessible? && !predicate_matches?
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
failure_message_expecting(true)
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
failure_message_expecting(false)
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
-
end
-
-
1
private
-
-
1
def predicate_accessible?
-
2
!private_predicate? && predicate_exists?
-
end
-
-
# support 1.8.7, evaluate once at load time for performance
-
1
if String === methods.first
-
def private_predicate?
-
@actual.private_methods.include? predicate.to_s
-
end
-
else
-
1
def private_predicate?
-
2
@actual.private_methods.include? predicate
-
end
-
end
-
-
1
def predicate_exists?
-
2
actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
-
end
-
-
1
def predicate_matches?
-
2
method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
-
2
@predicate_matches = actual.__send__(method_name, *@args, &@block)
-
end
-
-
1
def predicate
-
8
:"#{@expected}?"
-
end
-
-
1
def present_tense_predicate
-
:"#{@expected}s?"
-
end
-
-
1
def parse_expected(expected)
-
2
@prefix, expected = prefix_and_expected(expected)
-
2
expected
-
end
-
-
1
def prefix_and_expected(symbol)
-
2
Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
-
end
-
-
1
def prefix_to_sentence
-
split_words(@prefix)
-
end
-
-
1
def failure_message_expecting(value)
-
validity_message ||
-
"expected `#{@actual.inspect}.#{predicate}#{args_to_s}` to return #{value}, got #{@predicate_matches.inspect}"
-
end
-
-
1
def validity_message
-
if private_predicate?
-
"expected #{@actual} to respond to `#{predicate}` but `#{predicate}` is a private method"
-
elsif !predicate_exists?
-
"expected #{@actual} to respond to `#{predicate}`"
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `be_between`.
-
# Not intended to be instantiated directly.
-
1
class BeBetween < BaseMatcher
-
1
def initialize(min, max)
-
2
@min, @max = min, max
-
2
inclusive
-
end
-
-
# @api public
-
# Makes the between comparison inclusive.
-
#
-
# @example
-
# expect(3).to be_between(2, 3).inclusive
-
#
-
# @note The matcher is inclusive by default; this simply provides
-
# a way to be more explicit about it.
-
1
def inclusive
-
2
@less_than_operator = :<=
-
2
@greater_than_operator = :>=
-
2
@mode = :inclusive
-
2
self
-
end
-
-
# @api public
-
# Makes the between comparison exclusive.
-
#
-
# @example
-
# expect(3).to be_between(2, 4).exclusive
-
1
def exclusive
-
@less_than_operator = :<
-
@greater_than_operator = :>
-
@mode = :exclusive
-
self
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def matches?(actual)
-
2
@actual = actual
-
2
comparable? && compare
-
rescue ArgumentError
-
false
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"#{super}#{not_comparable_clause}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"be between #{@min.inspect} and #{@max.inspect} (#{@mode})"
-
end
-
-
1
private
-
-
1
def comparable?
-
2
@actual.respond_to?(@less_than_operator) && @actual.respond_to?(@greater_than_operator)
-
end
-
-
1
def not_comparable_clause
-
", but it does not respond to `#{@less_than_operator}` and `#{@greater_than_operator}`" unless comparable?
-
end
-
-
1
def compare
-
2
@actual.__send__(@greater_than_operator, @min) && @actual.__send__(@less_than_operator, @max)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `be_a_kind_of`.
-
# Not intended to be instantiated directly.
-
1
class BeAKindOf < BaseMatcher
-
1
private
-
-
1
def match(expected, actual)
-
22
actual.kind_of? expected
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `eq`.
-
# Not intended to be instantiated directly.
-
1
class Eq < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"\nexpected: #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"\nexpected: value != #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"#{name_to_sentence} #{@expected.inspect}"
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
true
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
38
actual == expected
-
end
-
-
1
def format_object(object)
-
if Time === object
-
format_time(object)
-
elsif defined?(DateTime) && DateTime === object
-
format_date_time(object)
-
elsif defined?(BigDecimal) && BigDecimal === object
-
"#{object.to_s 'F'} (#{object.inspect})"
-
else
-
object.inspect
-
end
-
end
-
-
1
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
-
-
1
if Time.method_defined?(:nsec)
-
1
def format_time(time)
-
time.strftime("#{TIME_FORMAT}.#{"%09d" % time.nsec} %z")
-
end
-
else # for 1.8.7
-
def format_time(time)
-
time.strftime("#{TIME_FORMAT}.#{"%06d" % time.usec} %z")
-
end
-
end
-
-
1
DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
-
# ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
-
# defined use a custom format string that includes more time precision.
-
1
def format_date_time(date_time)
-
if defined?(ActiveSupport)
-
date_time.strftime(DATE_TIME_FORMAT)
-
else
-
date_time.inspect
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `eql`.
-
# Not intended to be instantiated directly.
-
1
class Eql < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"\nexpected: #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"\nexpected: value != #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
true
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
2
actual.eql? expected
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `equal`.
-
# Not intended to be instantiated directly.
-
1
class Equal < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
if expected_is_a_literal_singleton?
-
simple_failure_message
-
else
-
detailed_failure_message
-
end
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
<<-MESSAGE
-
-
expected not #{inspect_object(actual)}
-
got #{inspect_object(expected)}
-
-
Compared using equal?, which compares object identity.
-
-
MESSAGE
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
!expected_is_a_literal_singleton?
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
11
actual.equal? expected
-
end
-
-
1
LITERAL_SINGLETONS = [true, false, nil]
-
-
1
def expected_is_a_literal_singleton?
-
LITERAL_SINGLETONS.include?(expected)
-
end
-
-
1
def actual_inspected
-
if LITERAL_SINGLETONS.include?(actual)
-
actual.inspect
-
else
-
inspect_object(actual)
-
end
-
end
-
-
1
def simple_failure_message
-
"\nexpected #{expected.inspect}\n got #{actual_inspected}\n"
-
end
-
-
1
def detailed_failure_message
-
<<-MESSAGE
-
-
expected #{inspect_object(expected)}
-
got #{inspect_object(actual)}
-
-
Compared using equal?, which compares object identity,
-
but expected and actual are not the same object. Use
-
`expect(actual).to eq(expected)` if you don't care about
-
object identity in this example.
-
-
MESSAGE
-
end
-
-
1
def inspect_object(o)
-
"#<#{o.class}:#{o.object_id}> => #{o.inspect}"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `raise_error`.
-
# Not intended to be instantiated directly.
-
# rubocop:disable ClassLength
-
1
class RaiseError
-
1
include Composable
-
-
1
def initialize(expected_error_or_message=Exception, expected_message=nil, &block)
-
18
@block = block
-
18
@actual_error = nil
-
18
case expected_error_or_message
-
when String, Regexp
-
@expected_error, @expected_message = Exception, expected_error_or_message
-
else
-
18
@expected_error, @expected_message = expected_error_or_message, expected_message
-
end
-
end
-
-
# @api public
-
# Specifies the expected error message.
-
1
def with_message(expected_message)
-
raise_message_already_set if @expected_message
-
@expected_message = expected_message
-
self
-
end
-
-
# rubocop:disable MethodLength
-
# @private
-
1
def matches?(given_proc, negative_expectation=false, &block)
-
18
@given_proc = given_proc
-
18
@block ||= block
-
18
@raised_expected_error = false
-
18
@with_expected_message = false
-
18
@eval_block = false
-
18
@eval_block_passed = false
-
-
18
return false unless Proc === given_proc
-
-
18
begin
-
18
given_proc.call
-
rescue Exception => @actual_error
-
17
if values_match?(@expected_error, @actual_error)
-
17
@raised_expected_error = true
-
17
@with_expected_message = verify_message
-
end
-
end
-
-
18
unless negative_expectation
-
17
eval_block if @raised_expected_error && @with_expected_message && @block
-
end
-
-
18
expectation_matched?
-
end
-
# rubocop:enable MethodLength
-
-
# @private
-
1
def does_not_match?(given_proc)
-
1
prevent_invalid_expectations
-
1
!matches?(given_proc, :negative_expectation) && Proc === given_proc
-
end
-
-
# @private
-
1
def supports_block_expectations?
-
18
true
-
end
-
-
1
def expects_call_stack_jump?
-
true
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
@eval_block ? @actual_error.message : "expected #{expected_error}#{given_error}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected no #{expected_error}#{given_error}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"raise #{expected_error}"
-
end
-
-
1
private
-
-
1
def expectation_matched?
-
18
error_and_message_match? && block_matches?
-
end
-
-
1
def error_and_message_match?
-
18
@raised_expected_error && @with_expected_message
-
end
-
-
1
def block_matches?
-
17
@eval_block ? @eval_block_passed : true
-
end
-
-
1
def eval_block
-
@eval_block = true
-
begin
-
@block[@actual_error]
-
@eval_block_passed = true
-
rescue Exception => err
-
@actual_error = err
-
end
-
end
-
-
1
def verify_message
-
17
return true if @expected_message.nil?
-
values_match?(@expected_message, @actual_error.message)
-
end
-
-
1
def prevent_invalid_expectations
-
1
what_to_raise = if expecting_specific_exception? && @expected_message
-
"`expect { }.not_to raise_error(SpecificErrorClass, message)`"
-
elsif expecting_specific_exception?
-
"`expect { }.not_to raise_error(SpecificErrorClass)`"
-
elsif @expected_message
-
"`expect { }.not_to raise_error(message)`"
-
end
-
-
1
return unless what_to_raise
-
-
specific_class_error = ArgumentError.new("#{what_to_raise} is not valid, use `expect { }.not_to raise_error` (with no args) instead")
-
raise specific_class_error
-
end
-
-
1
def expected_error
-
case @expected_message
-
when nil
-
description_of(@expected_error)
-
when Regexp
-
"#{@expected_error} with message matching #{@expected_message.inspect}"
-
else
-
"#{@expected_error} with #{description_of @expected_message}"
-
end
-
end
-
-
1
def format_backtrace(backtrace)
-
formatter = Matchers.configuration.backtrace_formatter
-
formatter.format_backtrace(backtrace)
-
end
-
-
1
def given_error
-
return " but was not given a block" unless Proc === @given_proc
-
return " but nothing was raised" unless @actual_error
-
-
backtrace = format_backtrace(@actual_error.backtrace)
-
[
-
", got #{@actual_error.inspect} with backtrace:",
-
*backtrace
-
].join("\n # ")
-
end
-
-
1
def expecting_specific_exception?
-
2
@expected_error != Exception
-
end
-
-
1
def raise_message_already_set
-
raise "`expect { }.to raise_error(message).with_message(message)` is not valid. The matcher only allows the expected message to be specified once"
-
end
-
end
-
# rubocop:enable ClassLength
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support "method_signature_verifier"
-
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `respond_to`.
-
# Not intended to be instantiated directly.
-
1
class RespondTo < BaseMatcher
-
1
def initialize(*names)
-
1
@names = names
-
1
@expected_arity = nil
-
end
-
-
# @api public
-
# Specifies the number of expected arguments.
-
#
-
# @example
-
# expect(obj).to respond_to(:message).with(3).arguments
-
1
def with(n)
-
@expected_arity = n
-
self
-
end
-
-
# @api public
-
# No-op. Intended to be used as syntactic sugar when using `with`.
-
#
-
# @example
-
# expect(obj).to respond_to(:message).with(3).arguments
-
1
def argument
-
self
-
end
-
1
alias :arguments :argument
-
-
# @private
-
1
def matches?(actual)
-
1
find_failing_method_names(actual, :reject).empty?
-
end
-
-
# @private
-
1
def does_not_match?(actual)
-
find_failing_method_names(actual, :select).empty?
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected #{@actual.inspect} to respond to #{@failing_method_names.map { |name| name.inspect }.join(', ')}#{with_arity}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
failure_message.sub(/to respond to/, 'not to respond to')
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"respond to #{pp_names}#{with_arity}"
-
end
-
-
1
private
-
-
1
def find_failing_method_names(actual, filter_method)
-
1
@actual = actual
-
1
@failing_method_names = @names.__send__(filter_method) do |name|
-
1
@actual.respond_to?(name) && matches_arity?(actual, name)
-
end
-
end
-
-
1
def matches_arity?(actual, name)
-
1
return true unless @expected_arity
-
-
signature = Support::MethodSignature.new(actual.method(name))
-
Support::StrictSignatureVerifier.new(signature, Array.new(@expected_arity)).valid?
-
end
-
-
1
def with_arity
-
return "" unless @expected_arity
-
" with #{@expected_arity} argument#{@expected_arity == 1 ? '' : 's'}"
-
end
-
-
1
def pp_names
-
# Ruby 1.9 returns the same thing for array.to_s as array.inspect, so just use array.inspect here
-
@names.length == 1 ? "##{@names.first}" : @names.inspect
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support "fuzzy_matcher"
-
-
1
module RSpec
-
1
module Matchers
-
# Mixin designed to support the composable matcher features
-
# of RSpec 3+. Mix it into your custom matcher classes to
-
# allow them to be used in a composable fashion.
-
#
-
# @api public
-
1
module Composable
-
# Creates a compound `and` expectation. The matcher will
-
# only pass if both sub-matchers pass.
-
# This can be chained together to form an arbitrarily long
-
# chain of matchers.
-
#
-
# @example
-
# expect(alphabet).to start_with("a").and end_with("z")
-
# expect(alphabet).to start_with("a") & end_with("z")
-
#
-
# @note The negative form (`expect(...).not_to matcher.and other`)
-
# is not supported at this time.
-
1
def and(matcher)
-
BuiltIn::Compound::And.new self, matcher
-
end
-
1
alias & and
-
-
# Creates a compound `or` expectation. The matcher will
-
# pass if either sub-matcher passes.
-
# This can be chained together to form an arbitrarily long
-
# chain of matchers.
-
#
-
# @example
-
# expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
-
# expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")
-
#
-
# @note The negative form (`expect(...).not_to matcher.or other`)
-
# is not supported at this time.
-
1
def or(matcher)
-
BuiltIn::Compound::Or.new self, matcher
-
end
-
1
alias | or
-
-
# Delegates to `#matches?`. Allows matchers to be used in composable
-
# fashion and also supports using matchers in case statements.
-
1
def ===(value)
-
matches?(value)
-
end
-
-
1
private
-
-
# This provides a generic way to fuzzy-match an expected value against
-
# an actual value. It understands nested data structures (e.g. hashes
-
# and arrays) and is able to match against a matcher being used as
-
# the expected value or within the expected value at any level of
-
# nesting.
-
#
-
# Within a custom matcher you are encouraged to use this whenever your
-
# matcher needs to match two values, unless it needs more precise semantics.
-
# For example, the `eq` matcher _does not_ use this as it is meant to
-
# use `==` (and only `==`) for matching.
-
#
-
# @param expected [Object] what is expected
-
# @param actual [Object] the actual value
-
#
-
# @!visibility public
-
1
def values_match?(expected, actual)
-
17
expected = with_matchers_cloned(expected)
-
17
Support::FuzzyMatcher.values_match?(expected, actual)
-
end
-
-
# Returns the description of the given object in a way that is
-
# aware of composed matchers. If the object is a matcher with
-
# a `description` method, returns the description; otherwise
-
# returns `object.inspect`.
-
#
-
# You are encouraged to use this in your custom matcher's
-
# `description`, `failure_message` or
-
# `failure_message_when_negated` implementation if you are
-
# supporting matcher arguments.
-
#
-
# @!visibility public
-
1
def description_of(object)
-
return object.description if Matchers.is_a_describable_matcher?(object)
-
object.inspect
-
end
-
-
# Transforms the given data structue (typically a hash or array)
-
# into a new data structure that, when `#inspect` is called on it,
-
# will provide descriptions of any contained matchers rather than
-
# the normal `#inspect` output.
-
#
-
# You are encouraged to use this in your custom matcher's
-
# `description`, `failure_message` or
-
# `failure_message_when_negated` implementation if you are
-
# supporting any arguments which may be a data structure
-
# containing matchers.
-
#
-
# @!visibility public
-
1
def surface_descriptions_in(item)
-
if Matchers.is_a_describable_matcher?(item)
-
DescribableItem.new(item)
-
elsif Hash === item
-
Hash[surface_descriptions_in(item.to_a)]
-
elsif Struct === item
-
item.inspect
-
elsif enumerable?(item)
-
begin
-
item.map { |subitem| surface_descriptions_in(subitem) }
-
rescue IOError # STDOUT is enumerable but `map` raises an error
-
item.inspect
-
end
-
else
-
item
-
end
-
end
-
-
# @private
-
# Historically, a single matcher instance was only checked
-
# against a single value. Given that the matcher was only
-
# used once, it's been common to memoize some intermediate
-
# calculation that is derived from the `actual` value in
-
# order to reuse that intermediate result in the failure
-
# message.
-
#
-
# This can cause a problem when using such a matcher as an
-
# argument to another matcher in a composed matcher expression,
-
# since the matcher instance may be checked against multiple
-
# values and produce invalid results due to the memoization.
-
#
-
# To deal with this, we clone any matchers in `expected` via
-
# this method when using `values_match?`, so that any memoization
-
# does not "leak" between checks.
-
1
def with_matchers_cloned(object)
-
17
if Matchers.is_a_matcher?(object)
-
object.clone
-
17
elsif Hash === object
-
Hash[with_matchers_cloned(object.to_a)]
-
17
elsif Struct === object
-
object
-
17
elsif enumerable?(object)
-
begin
-
object.map { |subobject| with_matchers_cloned(subobject) }
-
rescue IOError # STDOUT is enumerable but `map` raises an error
-
object
-
end
-
else
-
17
object
-
end
-
end
-
-
1
if String.ancestors.include?(Enumerable) # 1.8.7
-
# Strings are not enumerable on 1.9, and on 1.8 they are an infinitely
-
# nested enumerable: since ruby lacks a character class, it yields
-
# 1-character strings, which are themselves enumerable, composed of a
-
# a single 1-character string, which is an enumerable, etc.
-
#
-
# @api private
-
def enumerable?(item)
-
return false if String === item
-
Enumerable === item
-
end
-
else
-
# @api private
-
1
def enumerable?(item)
-
17
Enumerable === item
-
end
-
end
-
1
module_function :surface_descriptions_in, :enumerable?
-
-
# Wraps an item in order to surface its `description` via `inspect`.
-
# @api private
-
1
DescribableItem = Struct.new(:item) do
-
1
def inspect
-
"(#{item.description})"
-
end
-
-
1
def pretty_print(pp)
-
pp.text "(#{item.description})"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Defines the custom matcher DSL.
-
1
module DSL
-
# Defines a custom matcher.
-
# @see RSpec::Matchers
-
1
def define(name, &declarations)
-
define_method name do |*expected|
-
RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected)
-
end
-
end
-
1
alias_method :matcher, :define
-
-
2
RSpec.configure { |c| c.extend self } if RSpec.respond_to?(:configure)
-
-
# Contains the methods that are available from within the
-
# `RSpec::Matchers.define` DSL for creating custom matchers.
-
1
module Macros
-
# Stores the block that is used to determine whether this matcher passes
-
# or fails. The block should return a boolean value. When the matcher is
-
# passed to `expect(...).to` and the block returns `true`, then the expectation
-
# passes. Similarly, when the matcher is passed to `expect(...).not_to` and the
-
# block returns `false`, then the expectation passes.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :be_even do
-
# match do |actual|
-
# actual.even?
-
# end
-
# end
-
#
-
# expect(4).to be_even # passes
-
# expect(3).not_to be_even # passes
-
# expect(3).to be_even # fails
-
# expect(4).not_to be_even # fails
-
#
-
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
-
1
def match(&match_block)
-
define_user_override(:matches?, match_block) do |actual|
-
begin
-
@actual = actual
-
super(*actual_arg_for(match_block))
-
rescue RSpec::Expectations::ExpectationNotMetError
-
false
-
end
-
end
-
end
-
-
# Use this to define the block for a negative expectation (`expect(...).not_to`)
-
# when the positive and negative forms require different handling. This
-
# is rarely necessary, but can be helpful, for example, when specifying
-
# asynchronous processes that require different timeouts.
-
#
-
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
-
1
def match_when_negated(&match_block)
-
define_user_override(:does_not_match?, match_block) do |actual|
-
@actual = actual
-
super(*actual_arg_for(match_block))
-
end
-
end
-
-
# Use this instead of `match` when the block will raise an exception
-
# rather than returning false to indicate a failure.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :accept_as_valid do |candidate_address|
-
# match_unless_raises ValidationException do |validator|
-
# validator.validate(candidate_address)
-
# end
-
# end
-
#
-
# expect(email_validator).to accept_as_valid("person@company.com")
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def match_unless_raises(expected_exception=Exception, &match_block)
-
define_user_override(:matches?, match_block) do |actual|
-
@actual = actual
-
begin
-
super(*actual_arg_for(match_block))
-
rescue expected_exception => @rescued_exception
-
false
-
else
-
true
-
end
-
end
-
end
-
-
# Customizes the failure messsage to use when this matcher is
-
# asked to positively match. Only use this when the message
-
# generated by default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_strength do |expected|
-
# match { your_match_logic }
-
#
-
# failure_message do |actual|
-
# "Expected strength of #{expected}, but had #{actual.strength}"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def failure_message(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Customize the failure messsage to use when this matcher is asked
-
# to negatively match. Only use this when the message generated by
-
# default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_strength do |expected|
-
# match { your_match_logic }
-
#
-
# failure_message_when_negated do |actual|
-
# "Expected not to have strength of #{expected}, but did"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def failure_message_when_negated(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Customize the description to use for one-liners. Only use this when
-
# the description generated by default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :qualify_for do |expected|
-
# match { your_match_logic }
-
#
-
# description do
-
# "qualify for #{expected}"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def description(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Tells the matcher to diff the actual and expected values in the failure
-
# message.
-
1
def diffable
-
define_method(:diffable?) { true }
-
end
-
-
# Declares that the matcher can be used in a block expectation.
-
# Users will not be able to use your matcher in a block
-
# expectation without declaring this.
-
# (e.g. `expect { do_something }.to matcher`).
-
1
def supports_block_expectations
-
define_method(:supports_block_expectations?) { true }
-
end
-
-
# Convenience for defining methods on this matcher to create a fluent
-
# interface. The trick about fluent interfaces is that each method must
-
# return self in order to chain methods together. `chain` handles that
-
# for you. If the method is invoked and the
-
# `include_chain_clauses_in_custom_matcher_descriptions` config option
-
# hash been enabled, the chained method name and args will be added to the
-
# default description and failure message.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_errors_on do |key|
-
# chain :with do |message|
-
# @message = message
-
# end
-
#
-
# match do |actual|
-
# actual.errors[key] == @message
-
# end
-
# end
-
#
-
# expect(minor).to have_errors_on(:age).with("Not old enough to participate")
-
1
def chain(name, &definition)
-
define_user_override(name, definition) do |*args, &block|
-
super(*args, &block)
-
@chained_method_clauses.push([name, args])
-
self
-
end
-
end
-
-
1
private
-
-
# Does the following:
-
#
-
# - Defines the named method usign a user-provided block
-
# in @user_method_defs, which is included as an ancestor
-
# in the singleton class in which we eval the `define` block.
-
# - Defines an overriden definition for the same method
-
# usign the provided `our_def` block.
-
# - Provides a default `our_def` block for the common case
-
# of needing to call the user's definition with `@actual`
-
# as an arg, but only if their block's arity can handle it.
-
#
-
# This compiles the user block into an actual method, allowing
-
# them to use normal method constructs like `return`
-
# (e.g. for a early guard statement), while allowing us to define
-
# an override that can provide the wrapped handling
-
# (e.g. assigning `@actual`, rescueing errors, etc) and
-
# can `super` to the user's definition.
-
1
def define_user_override(method_name, user_def, &our_def)
-
@user_method_defs.__send__(:define_method, method_name, &user_def)
-
our_def ||= lambda { super(*actual_arg_for(user_def)) }
-
define_method(method_name, &our_def)
-
end
-
-
# Defines deprecated macro methods from RSpec 2 for backwards compatibility.
-
# @deprecated Use the methods from {Macros} instead.
-
1
module Deprecated
-
# @deprecated Use {Macros#match} instead.
-
1
def match_for_should(&definition)
-
RSpec.deprecate("`match_for_should`", :replacement => "`match`")
-
match(&definition)
-
end
-
-
# @deprecated Use {Macros#match_when_negated} instead.
-
1
def match_for_should_not(&definition)
-
RSpec.deprecate("`match_for_should_not`", :replacement => "`match_when_negated`")
-
match_when_negated(&definition)
-
end
-
-
# @deprecated Use {Macros#failure_message} instead.
-
1
def failure_message_for_should(&definition)
-
RSpec.deprecate("`failure_message_for_should`", :replacement => "`failure_message`")
-
failure_message(&definition)
-
end
-
-
# @deprecated Use {Macros#failure_message_when_negated} instead.
-
1
def failure_message_for_should_not(&definition)
-
RSpec.deprecate("`failure_message_for_should_not`", :replacement => "`failure_message_when_negated`")
-
failure_message_when_negated(&definition)
-
end
-
end
-
end
-
-
# Defines default implementations of the matcher
-
# protocol methods for custom matchers. You can
-
# override any of these using the {RSpec::Matchers::DSL::Macros Macros} methods
-
# from within an `RSpec::Matchers.define` block.
-
1
module DefaultImplementations
-
1
include BuiltIn::BaseMatcher::DefaultFailureMessages
-
-
# @api private
-
# Used internally by objects returns by `should` and `should_not`.
-
1
def diffable?
-
false
-
end
-
-
# The default description.
-
1
def description
-
"#{name_to_sentence}#{to_sentence expected}#{chained_method_clause_sentences}"
-
end
-
-
# Matchers do not support block expectations by default. You
-
# must opt-in.
-
1
def supports_block_expectations?
-
false
-
end
-
-
# Most matchers do not expect call stack jumps.
-
1
def expects_call_stack_jump?
-
false
-
end
-
-
1
private
-
-
1
def chained_method_clause_sentences
-
return '' unless Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions?
-
-
@chained_method_clauses.map do |(method_name, method_args)|
-
" #{split_words(method_name)}#{to_sentence(method_args)}"
-
end.join
-
end
-
end
-
-
# The class used for custom matchers. The block passed to
-
# `RSpec::Matchers.define` will be evaluated in the context
-
# of the singleton class of an instance, and will have the
-
# {RSpec::Matchers::DSL::Macros Macros} methods available.
-
1
class Matcher
-
# Provides default implementations for the matcher protocol methods.
-
1
include DefaultImplementations
-
-
# Allows expectation expressions to be used in the match block.
-
1
include RSpec::Matchers
-
-
# Converts matcher name and expected args to an English expresion.
-
1
include RSpec::Matchers::Pretty
-
-
# Supports the matcher composability features of RSpec 3+.
-
1
include Composable
-
-
# Makes the macro methods available to an `RSpec::Matchers.define` block.
-
1
extend Macros
-
1
extend Macros::Deprecated
-
-
# Exposes the value being matched against -- generally the object
-
# object wrapped by `expect`.
-
1
attr_reader :actual
-
-
# Exposes the exception raised during the matching by `match_unless_raises`.
-
# Could be useful to extract details for a failure message.
-
1
attr_reader :rescued_exception
-
-
# @api private
-
1
def initialize(name, declarations, matcher_execution_context, *expected)
-
@name = name
-
@actual = nil
-
@expected_as_array = expected
-
@matcher_execution_context = matcher_execution_context
-
@chained_method_clauses = []
-
-
class << self
-
# See `Macros#define_user_override` above, for an explanation.
-
include(@user_method_defs = Module.new)
-
self
-
end.class_exec(*expected, &declarations)
-
end
-
-
# Provides the expected value. This will return an array if
-
# multiple arguments were passed to the matcher; otherwise it
-
# will return a single value.
-
# @see #expected_as_array
-
1
def expected
-
if expected_as_array.size == 1
-
expected_as_array[0]
-
else
-
expected_as_array
-
end
-
end
-
-
# Returns the expected value as an an array. This exists primarily
-
# to aid in upgrading from RSpec 2.x, since in RSpec 2, `expected`
-
# always returned an array.
-
# @see #expected
-
1
attr_reader :expected_as_array
-
-
# Adds the name (rather than a cryptic hex number)
-
# so we can identify an instance of
-
# the matcher in error messages (e.g. for `NoMethodError`)
-
1
def inspect
-
"#<#{self.class.name} #{name}>"
-
end
-
-
1
if RUBY_VERSION.to_f >= 1.9
-
# Indicates that this matcher responds to messages
-
# from the `@matcher_execution_context` as well.
-
# Also, supports getting a method object for such methods.
-
1
def respond_to_missing?(method, include_private=false)
-
super || @matcher_execution_context.respond_to?(method, include_private)
-
end
-
else # for 1.8.7
-
# Indicates that this matcher responds to messages
-
# from the `@matcher_execution_context` as well.
-
def respond_to?(method, include_private=false)
-
super || @matcher_execution_context.respond_to?(method, include_private)
-
end
-
end
-
-
1
private
-
-
1
def actual_arg_for(block)
-
block.arity.zero? ? [] : [@actual]
-
end
-
-
# Takes care of forwarding unhandled messages to the
-
# `@matcher_execution_context` (typically the current
-
# running `RSpec::Core::Example`). This is needed by
-
# rspec-rails so that it can define matchers that wrap
-
# Rails' test helper methods, but it's also a useful
-
# feature in its own right.
-
1
def method_missing(method, *args, &block)
-
if @matcher_execution_context.respond_to?(method)
-
@matcher_execution_context.__send__ method, *args, &block
-
else
-
super(method, *args, &block)
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
RSpec::Matchers.extend RSpec::Matchers::DSL
-
1
module RSpec
-
1
module Matchers
-
1
class << self
-
# @private
-
1
attr_accessor :last_matcher, :last_expectation_handler
-
end
-
-
# @api private
-
# Used by rspec-core to clear the state used to generate
-
# descriptions after an example.
-
1
def self.clear_generated_description
-
63
self.last_matcher = nil
-
63
self.last_expectation_handler = nil
-
end
-
-
# @api private
-
# Generates an an example description based on the last expectation.
-
# Used by rspec-core's one-liner syntax.
-
1
def self.generated_description
-
return nil if last_expectation_handler.nil?
-
"#{last_expectation_handler.verb} #{last_description}"
-
end
-
-
1
private
-
-
1
def self.last_description
-
last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE
-
When you call a matcher in an example without a String, like this:
-
-
specify { expect(object).to matcher }
-
-
or this:
-
-
it { is_expected.to matcher }
-
-
RSpec expects the matcher to have a #description method. You should either
-
add a String to the example this matcher is being used in, or give it a
-
description method. Then you won't have to suffer this lengthy warning again.
-
MESSAGE
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Provides the necessary plumbing to wrap a matcher with a decorator.
-
# @private
-
1
class MatcherDelegator
-
1
include Composable
-
1
attr_reader :base_matcher
-
-
1
def initialize(base_matcher)
-
@base_matcher = base_matcher
-
end
-
-
1
def method_missing(*args, &block)
-
base_matcher.__send__(*args, &block)
-
end
-
-
1
if ::RUBY_VERSION.to_f > 1.8
-
1
def respond_to_missing?(name, include_all=false)
-
super || base_matcher.respond_to?(name, include_all)
-
end
-
else
-
def respond_to?(name, include_all=false)
-
super || base_matcher.respond_to?(name, include_all)
-
end
-
end
-
-
1
def initialize_copy(other)
-
@base_matcher = @base_matcher.clone
-
super
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# @api private
-
# Contains logic to facilitate converting ruby symbols and
-
# objects to english phrases.
-
1
module Pretty
-
# @api private
-
# Converts a symbol into an english expression.
-
1
def split_words(sym)
-
sym.to_s.gsub(/_/, ' ')
-
end
-
1
module_function :split_words
-
-
# @api private
-
# Converts a collection of objects into an english expression.
-
1
def to_sentence(words)
-
return " #{words.inspect}" if !words || Struct === words
-
words = Array(words).map { |w| to_word(w) }
-
case words.length
-
when 0
-
""
-
when 1
-
" #{words[0]}"
-
when 2
-
" #{words[0]} and #{words[1]}"
-
else
-
" #{words[0...-1].join(', ')}, and #{words[-1]}"
-
end
-
end
-
-
# @api private
-
# Converts the given item to string suitable for use in a list expression.
-
1
def to_word(item)
-
is_matcher_with_description?(item) ? item.description : item.inspect
-
end
-
-
# @private
-
# Provides an English expression for the matcher name.
-
1
def name_to_sentence
-
split_words(name)
-
end
-
-
# @api private
-
# Provides a name for the matcher.
-
1
def name
-
defined?(@name) ? @name : underscore(self.class.name.split("::").last)
-
end
-
-
# @private
-
# Borrowed from ActiveSupport
-
1
def underscore(camel_cased_word)
-
word = camel_cased_word.to_s.dup
-
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
-
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
-
word.tr!("-", "_")
-
word.downcase!
-
word
-
end
-
-
1
private
-
-
1
def is_matcher_with_description?(object)
-
RSpec::Matchers.is_a_matcher?(object) && object.respond_to?(:description)
-
end
-
-
# `{ :a => 5, :b => 2 }.inspect` produces:
-
# {:a=>5, :b=>2}
-
# ...but it looks much better as:
-
# {:a => 5, :b => 2}
-
#
-
# This is idempotent and safe to run on a string multiple times.
-
1
def improve_hash_formatting(inspect_string)
-
inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2')
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support 'caller_filter'
-
1
RSpec::Support.require_rspec_support 'warnings'
-
1
RSpec::Support.require_rspec_support 'ruby_features'
-
-
31
RSpec::Support.define_optimized_require_for_rspec(:mocks) { |f| require_relative f }
-
-
%w[
-
instance_method_stasher
-
method_double
-
argument_matchers
-
example_methods
-
proxy
-
test_double
-
argument_list_matcher
-
message_expectation
-
order_group
-
error_generator
-
space
-
mutate_const
-
targets
-
syntax
-
configuration
-
verifying_double
-
version
-
18
].each { |name| RSpec::Support.require_rspec_mocks name }
-
-
# Share the top-level RSpec namespace, because we are a core supported
-
# extension.
-
1
module RSpec
-
# Contains top-level utility methods. While this contains a few
-
# public methods, these are not generally meant to be called from
-
# a test or example. They exist primarily for integration with
-
# test frameworks (such as rspec-core).
-
1
module Mocks
-
# Performs per-test/example setup. This should be called before
-
# an test or example begins.
-
1
def self.setup
-
63
@space_stack << (@space = space.new_scope)
-
end
-
-
# Verifies any message expectations that were set during the
-
# test or example. This should be called at the end of an example.
-
1
def self.verify
-
63
space.verify_all
-
end
-
-
# Cleans up all test double state (including any methods that were
-
# redefined on partial doubles). This _must_ be called after
-
# each example, even if an error was raised during the example.
-
1
def self.teardown
-
63
space.reset_all
-
63
@space_stack.pop
-
63
@space = @space_stack.last || @root_space
-
end
-
-
# Adds an allowance (stub) on `subject`
-
#
-
# @param subject the subject to which the message will be added
-
# @param message a symbol, representing the message that will be
-
# added.
-
# @param opts a hash of options, :expected_from is used to set the
-
# original call site
-
# @yield an optional implementation for the allowance
-
#
-
# @example Defines the implementation of `foo` on `bar`, using the passed block
-
# x = 0
-
# RSpec::Mocks.allow_message(bar, :foo) { x += 1 }
-
1
def self.allow_message(subject, message, opts={}, &block)
-
space.proxy_for(subject).add_stub(message, opts, &block)
-
end
-
-
# Sets a message expectation on `subject`.
-
# @param subject the subject on which the message will be expected
-
# @param message a symbol, representing the message that will be
-
# expected.
-
# @param opts a hash of options, :expected_from is used to set the
-
# original call site
-
# @yield an optional implementation for the expectation
-
#
-
# @example Expect the message `foo` to receive `bar`, then call it
-
# RSpec::Mocks.expect_message(bar, :foo)
-
# bar.foo
-
1
def self.expect_message(subject, message, opts={}, &block)
-
space.proxy_for(subject).add_message_expectation(message, opts, &block)
-
end
-
-
# Call the passed block and verify mocks after it has executed. This allows
-
# mock usage in arbitrary places, such as a `before(:all)` hook.
-
1
def self.with_temporary_scope
-
setup
-
-
begin
-
yield
-
verify
-
ensure
-
teardown
-
end
-
end
-
-
1
class << self
-
# @private
-
1
attr_reader :space
-
end
-
1
@space_stack = []
-
1
@root_space = @space = RSpec::Mocks::RootSpace.new
-
-
# @private
-
1
IGNORED_BACKTRACE_LINE = 'this backtrace line is ignored'
-
-
# To speed up boot time a bit, delay loading optional or rarely
-
# used features until their first use.
-
1
autoload :AnyInstance, "rspec/mocks/any_instance"
-
1
autoload :ExpectChain, "rspec/mocks/message_chain"
-
1
autoload :StubChain, "rspec/mocks/message_chain"
-
1
autoload :MarshalExtension, "rspec/mocks/marshal_extension"
-
-
# Namespace for mock-related matchers.
-
1
module Matchers
-
1
autoload :HaveReceived, "rspec/mocks/matchers/have_received"
-
1
autoload :Receive, "rspec/mocks/matchers/receive"
-
1
autoload :ReceiveMessageChain, "rspec/mocks/matchers/receive_message_chain"
-
1
autoload :ReceiveMessages, "rspec/mocks/matchers/receive_messages"
-
end
-
end
-
end
-
%w[
-
any_instance/chain
-
any_instance/stub_chain
-
any_instance/stub_chain_chain
-
any_instance/expect_chain_chain
-
any_instance/expectation_chain
-
any_instance/message_chains
-
any_instance/recorder
-
any_instance/proxy
-
9
].each { |f| RSpec::Support.require_rspec_mocks(f) }
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
module AnyInstance
-
# @private
-
1
class Chain
-
1
def initialize(recorder, *args, &block)
-
@recorder = recorder
-
@expectation_args = args
-
@expectation_block = block
-
@argument_list_matcher = ArgumentListMatcher::MATCH_ALL
-
end
-
-
# @private
-
#
-
# Provides convenience methods for recording customizations on message
-
# expectations.
-
1
module Customizations
-
# @macro [attach] record
-
# @method $1(*args, &block)
-
# Records the `$1` message for playback against an instance that
-
# invokes a method stubbed or mocked using `any_instance`.
-
#
-
# @see RSpec::Mocks::MessageExpectation#$1
-
#
-
1
def self.record(method_name)
-
14
define_method(method_name) do |*args, &block|
-
record(method_name, *args, &block)
-
end
-
end
-
-
1
record :and_return
-
1
record :and_raise
-
1
record :and_throw
-
1
record :and_yield
-
1
record :and_call_original
-
1
record :with
-
1
record :once
-
1
record :twice
-
1
record :thrice
-
1
record :exactly
-
1
record :times
-
1
record :never
-
1
record :at_least
-
1
record :at_most
-
end
-
-
1
include Customizations
-
-
# @private
-
1
def playback!(instance)
-
message_expectation = create_message_expectation_on(instance)
-
messages.inject(message_expectation) do |object, message|
-
object.__send__(*message.first, &message.last)
-
end
-
end
-
-
# @private
-
1
def constrained_to_any_of?(*constraints)
-
constraints.any? do |constraint|
-
messages.any? do |message|
-
message.first.first == constraint
-
end
-
end
-
end
-
-
# @private
-
1
def matches_args?(*args)
-
@argument_list_matcher.args_match?(*args)
-
end
-
-
# @private
-
1
def expectation_fulfilled!
-
@expectation_fulfilled = true
-
end
-
-
1
def never
-
ErrorGenerator.raise_double_negation_error("expect_any_instance_of(MyClass)") if negated?
-
super
-
end
-
-
1
def with(*args, &block)
-
@argument_list_matcher = ArgumentListMatcher.new(*args)
-
super
-
end
-
-
1
private
-
-
1
def negated?
-
messages.any? { |(message, *_), _| message == :never }
-
end
-
-
1
def messages
-
@messages ||= []
-
end
-
-
1
def last_message
-
messages.last.first.first unless messages.empty?
-
end
-
-
1
def record(rspec_method_name, *args, &block)
-
verify_invocation_order(rspec_method_name, *args, &block)
-
messages << [args.unshift(rspec_method_name), block]
-
self
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
1
module AnyInstance
-
# @private
-
1
class ExpectChainChain < StubChain
-
1
def initialize(*args)
-
super
-
@expectation_fulfilled = false
-
end
-
-
1
def expectation_fulfilled?
-
@expectation_fulfilled
-
end
-
-
1
def playback!(instance)
-
super.tap { @expectation_fulfilled = true }
-
end
-
-
1
private
-
-
1
def create_message_expectation_on(instance)
-
::RSpec::Mocks::ExpectChain.expect_chain_on(instance, *@expectation_args, &@expectation_block)
-
end
-
-
1
def invocation_order
-
@invocation_order ||= {
-
:and_return => [nil],
-
:and_raise => [nil],
-
:and_yield => [nil]
-
}
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
1
module AnyInstance
-
# @private
-
1
class ExpectationChain < Chain
-
1
def expectation_fulfilled?
-
@expectation_fulfilled || constrained_to_any_of?(:never)
-
end
-
-
1
def initialize(*args, &block)
-
@expectation_fulfilled = false
-
super
-
end
-
-
1
private
-
-
1
def verify_invocation_order(_rspec_method_name, *_args, &_block)
-
end
-
end
-
-
# @private
-
1
class PositiveExpectationChain < ExpectationChain
-
1
private
-
-
1
def create_message_expectation_on(instance)
-
proxy = ::RSpec::Mocks.space.proxy_for(instance)
-
method_name, opts = @expectation_args
-
opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE)
-
-
me = proxy.add_message_expectation(method_name, opts, &@expectation_block)
-
if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks?
-
me.and_yield_receiver_to_implementation
-
end
-
-
me
-
end
-
-
1
def invocation_order
-
@invocation_order ||= {
-
:with => [nil],
-
:and_return => [:with, nil],
-
:and_raise => [:with, nil]
-
}
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
1
module AnyInstance
-
# @private
-
1
class MessageChains
-
1
def initialize
-
@chains_by_method_name = Hash.new { |h, k| h[k] = [] }
-
end
-
-
# @private
-
1
def [](method_name)
-
@chains_by_method_name[method_name]
-
end
-
-
# @private
-
1
def add(method_name, chain)
-
@chains_by_method_name[method_name] << chain
-
chain
-
end
-
-
# @private
-
1
def remove_stub_chains_for!(method_name)
-
@chains_by_method_name[method_name].reject! do |chain|
-
StubChain === chain
-
end
-
end
-
-
# @private
-
1
def has_expectation?(method_name)
-
@chains_by_method_name[method_name].find do |chain|
-
ExpectationChain === chain
-
end
-
end
-
-
# @private
-
1
def each_unfulfilled_expectation_matching(method_name, *args)
-
@chains_by_method_name[method_name].each do |chain|
-
yield chain if !chain.expectation_fulfilled? && chain.matches_args?(*args)
-
end
-
end
-
-
# @private
-
1
def all_expectations_fulfilled?
-
@chains_by_method_name.all? do |_method_name, chains|
-
chains.all? { |chain| chain.expectation_fulfilled? }
-
end
-
end
-
-
# @private
-
1
def unfulfilled_expectations
-
@chains_by_method_name.map do |method_name, chains|
-
method_name.to_s if ExpectationChain === chains.last unless chains.last.expectation_fulfilled?
-
end.compact
-
end
-
-
# @private
-
1
def received_expected_message!(method_name)
-
@chains_by_method_name[method_name].each do |chain|
-
chain.expectation_fulfilled!
-
end
-
end
-
-
# @private
-
1
def playback!(instance, method_name)
-
raise_if_second_instance_to_receive_message(instance)
-
@chains_by_method_name[method_name].each do |chain|
-
chain.playback!(instance)
-
end
-
end
-
-
1
private
-
-
1
def raise_if_second_instance_to_receive_message(instance)
-
@instance_with_expectation ||= instance if ExpectationChain === instance
-
return unless ExpectationChain === instance
-
return if @instance_with_expectation.equal?(instance)
-
-
raise RSpec::Mocks::MockExpectationError,
-
"Exactly one instance should have received the following " \
-
"message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
1
module AnyInstance
-
# @private
-
# The `AnyInstance::Recorder` is responsible for redefining the klass's
-
# instance method in order to add any stubs/expectations the first time
-
# the method is called. It's not capable of updating a stub on an instance
-
# that's already been previously stubbed (either directly, or via
-
# `any_instance`).
-
#
-
# This proxy sits in front of the recorder and delegates both to it
-
# and to the `RSpec::Mocks::Proxy` for each already mocked or stubbed
-
# instance of the class, in order to propogates changes to the instances.
-
#
-
# Note that unlike `RSpec::Mocks::Proxy`, this proxy class is stateless
-
# and is not persisted in `RSpec::Mocks.space`.
-
#
-
# Proxying for the message expectation fluent interface (typically chained
-
# off of the return value of one of these methods) is provided by the
-
# `FluentInterfaceProxy` class below.
-
1
class Proxy
-
1
def initialize(recorder, target_proxies)
-
@recorder = recorder
-
@target_proxies = target_proxies
-
end
-
-
1
def klass
-
@recorder.klass
-
end
-
-
1
def stub(method_name_or_method_map, &block)
-
if Hash === method_name_or_method_map
-
method_name_or_method_map.each do |method_name, return_value|
-
stub(method_name).and_return(return_value)
-
end
-
else
-
perform_proxying(__method__, [method_name_or_method_map], block) do |proxy|
-
proxy.add_stub(method_name_or_method_map, &block)
-
end
-
end
-
end
-
-
1
def unstub(method_name)
-
perform_proxying(__method__, [method_name], nil) do |proxy|
-
proxy.remove_stub_if_present(method_name)
-
end
-
end
-
-
1
def stub_chain(*chain, &block)
-
perform_proxying(__method__, chain, block) do |proxy|
-
Mocks::StubChain.stub_chain_on(proxy.object, *chain, &block)
-
end
-
end
-
-
1
def expect_chain(*chain, &block)
-
perform_proxying(__method__, chain, block) do |proxy|
-
Mocks::ExpectChain.expect_chain_on(proxy.object, *chain, &block)
-
end
-
end
-
-
1
def should_receive(method_name, &block)
-
perform_proxying(__method__, [method_name], block) do |proxy|
-
# Yeah, this is a bit odd...but if we used `add_message_expectation`
-
# then it would act like `expect_every_instance_of(klass).to receive`.
-
# The any_instance recorder takes care of validating that an instance
-
# received the message.
-
proxy.add_stub(method_name, &block)
-
end
-
end
-
-
1
def should_not_receive(method_name, &block)
-
perform_proxying(__method__, [method_name], block) do |proxy|
-
proxy.add_message_expectation(method_name, &block).never
-
end
-
end
-
-
1
private
-
-
1
def perform_proxying(method_name, args, block, &target_proxy_block)
-
recorder_value = @recorder.__send__(method_name, *args, &block)
-
proxy_values = @target_proxies.map(&target_proxy_block)
-
FluentInterfaceProxy.new([recorder_value] + proxy_values)
-
end
-
end
-
-
# @private
-
# Delegates messages to each of the given targets in order to
-
# provide the fluent interface that is available off of message
-
# expectations when dealing with `any_instance`.
-
#
-
# `targets` will typically contain 1 of the `AnyInstance::Recorder`
-
# return values and N `MessageExpectation` instances (one per instance
-
# of the `any_instance` klass).
-
1
class FluentInterfaceProxy
-
1
def initialize(targets)
-
@targets = targets
-
end
-
-
1
if RUBY_VERSION.to_f > 1.8
-
1
def respond_to_missing?(method_name, include_private=false)
-
super || @targets.first.respond_to?(method_name, include_private)
-
end
-
else
-
def respond_to?(method_name, include_private=false)
-
super || @targets.first.respond_to?(method_name, include_private)
-
end
-
end
-
-
1
def method_missing(*args, &block)
-
return_values = @targets.map { |t| t.__send__(*args, &block) }
-
FluentInterfaceProxy.new(return_values)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
1
module AnyInstance
-
# Given a class `TheClass`, `TheClass.any_instance` returns a `Recorder`,
-
# which records stubs and message expectations for later playback on
-
# instances of `TheClass`.
-
#
-
# Further constraints are stored in instances of [Chain](Chain).
-
#
-
# @see AnyInstance
-
# @see Chain
-
1
class Recorder
-
# @private
-
1
attr_reader :message_chains, :stubs, :klass
-
-
1
def initialize(klass)
-
@message_chains = MessageChains.new
-
@stubs = Hash.new { |hash, key| hash[key] = [] }
-
@observed_methods = []
-
@played_methods = {}
-
@klass = klass
-
@expectation_set = false
-
end
-
-
# Initializes the recording a stub to be played back against any
-
# instance of this object that invokes the submitted method.
-
#
-
# @see Methods#stub
-
1
def stub(method_name, &block)
-
observe!(method_name)
-
message_chains.add(method_name, StubChain.new(self, method_name, &block))
-
end
-
-
# Initializes the recording a stub chain to be played back against any
-
# instance of this object that invokes the method matching the first
-
# argument.
-
#
-
# @see Methods#stub_chain
-
1
def stub_chain(*method_names_and_optional_return_values, &block)
-
normalize_chain(*method_names_and_optional_return_values) do |method_name, args|
-
observe!(method_name)
-
message_chains.add(method_name, StubChainChain.new(self, *args, &block))
-
end
-
end
-
-
# @private
-
1
def expect_chain(*method_names_and_optional_return_values, &block)
-
@expectation_set = true
-
normalize_chain(*method_names_and_optional_return_values) do |method_name, args|
-
observe!(method_name)
-
message_chains.add(method_name, ExpectChainChain.new(self, *args, &block))
-
end
-
end
-
-
# Initializes the recording a message expectation to be played back
-
# against any instance of this object that invokes the submitted
-
# method.
-
#
-
# @see Methods#should_receive
-
1
def should_receive(method_name, &block)
-
@expectation_set = true
-
observe!(method_name)
-
message_chains.add(method_name, PositiveExpectationChain.new(self, method_name, &block))
-
end
-
-
# The opposite of `should_receive`
-
#
-
# @see Methods#should_not_receive
-
1
def should_not_receive(method_name, &block)
-
should_receive(method_name, &block).never
-
end
-
-
# Removes any previously recorded stubs, stub_chains or message
-
# expectations that use `method_name`.
-
#
-
# @see Methods#unstub
-
1
def unstub(method_name)
-
unless @observed_methods.include?(method_name.to_sym)
-
raise RSpec::Mocks::MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
-
end
-
message_chains.remove_stub_chains_for!(method_name)
-
stubs[method_name].clear
-
stop_observing!(method_name) unless message_chains.has_expectation?(method_name)
-
end
-
-
# @api private
-
#
-
# Used internally to verify that message expectations have been
-
# fulfilled.
-
1
def verify
-
return unless @expectation_set
-
return if message_chains.all_expectations_fulfilled?
-
-
raise RSpec::Mocks::MockExpectationError,
-
"Exactly one instance should have received the following " \
-
"message(s) but didn't: #{message_chains.unfulfilled_expectations.sort.join(', ')}"
-
end
-
-
# @private
-
1
def stop_all_observation!
-
@observed_methods.each { |method_name| restore_method!(method_name) }
-
end
-
-
# @private
-
1
def playback!(instance, method_name)
-
RSpec::Mocks.space.ensure_registered(instance)
-
message_chains.playback!(instance, method_name)
-
@played_methods[method_name] = instance
-
received_expected_message!(method_name) if message_chains.has_expectation?(method_name)
-
end
-
-
# @private
-
1
def instance_that_received(method_name)
-
@played_methods[method_name]
-
end
-
-
# @private
-
1
def build_alias_method_name(method_name)
-
"__#{method_name}_without_any_instance__"
-
end
-
-
# @private
-
1
def already_observing?(method_name)
-
@observed_methods.include?(method_name) || super_class_observing?(method_name)
-
end
-
-
# @private
-
1
def notify_received_message(_object, message, args, _blk)
-
has_expectation = false
-
-
message_chains.each_unfulfilled_expectation_matching(message, *args) do |expectation|
-
has_expectation = true
-
expectation.expectation_fulfilled!
-
end
-
-
return unless has_expectation
-
-
restore_method!(message)
-
mark_invoked!(message)
-
end
-
-
1
protected
-
-
1
def stop_observing!(method_name)
-
restore_method!(method_name)
-
@observed_methods.delete(method_name)
-
super_class_observers_for(method_name).each do |ancestor|
-
::RSpec::Mocks.space.
-
any_instance_recorder_for(ancestor).stop_observing!(method_name)
-
end
-
end
-
-
1
private
-
-
1
def ancestor_is_an_observer?(method_name)
-
lambda do |ancestor|
-
unless ancestor == @klass
-
::RSpec::Mocks.space.
-
any_instance_recorder_for(ancestor).already_observing?(method_name)
-
end
-
end
-
end
-
-
1
def super_class_observers_for(method_name)
-
@klass.ancestors.select(&ancestor_is_an_observer?(method_name))
-
end
-
-
1
def super_class_observing?(method_name)
-
@klass.ancestors.any?(&ancestor_is_an_observer?(method_name))
-
end
-
-
1
def normalize_chain(*args)
-
args.shift.to_s.split('.').map { |s| s.to_sym }.reverse.each { |a| args.unshift a }
-
yield args.first, args
-
end
-
-
1
def received_expected_message!(method_name)
-
message_chains.received_expected_message!(method_name)
-
restore_method!(method_name)
-
mark_invoked!(method_name)
-
end
-
-
1
def restore_method!(method_name)
-
if public_protected_or_private_method_defined?(build_alias_method_name(method_name))
-
restore_original_method!(method_name)
-
else
-
remove_dummy_method!(method_name)
-
end
-
end
-
-
1
def restore_original_method!(method_name)
-
return unless @klass.instance_method(method_name).owner == @klass
-
-
alias_method_name = build_alias_method_name(method_name)
-
@klass.class_exec do
-
remove_method method_name
-
alias_method method_name, alias_method_name
-
remove_method alias_method_name
-
end
-
end
-
-
1
def remove_dummy_method!(method_name)
-
@klass.class_exec do
-
remove_method method_name
-
end
-
end
-
-
1
def backup_method!(method_name)
-
alias_method_name = build_alias_method_name(method_name)
-
@klass.class_exec do
-
alias_method alias_method_name, method_name
-
end if public_protected_or_private_method_defined?(method_name)
-
end
-
-
1
def public_protected_or_private_method_defined?(method_name)
-
MethodReference.method_defined_at_any_visibility?(@klass, method_name)
-
end
-
-
1
def observe!(method_name)
-
allow_no_prepended_module_definition_of(method_name)
-
-
if RSpec::Mocks.configuration.verify_partial_doubles?
-
unless public_protected_or_private_method_defined?(method_name)
-
raise MockExpectationError,
-
"#{@klass} does not implement ##{method_name}"
-
end
-
end
-
-
stop_observing!(method_name) if already_observing?(method_name)
-
@observed_methods << method_name
-
backup_method!(method_name)
-
recorder = self
-
@klass.__send__(:define_method, method_name) do |*args, &blk|
-
recorder.playback!(self, method_name)
-
__send__(method_name, *args, &blk)
-
end
-
end
-
-
1
def mark_invoked!(method_name)
-
backup_method!(method_name)
-
recorder = self
-
@klass.__send__(:define_method, method_name) do |*_args, &_blk|
-
invoked_instance = recorder.instance_that_received(method_name)
-
inspect = "#<#{self.class}:#{object_id} #{instance_variables.map { |name| "#{name}=#{instance_variable_get name}" }.join(', ')}>"
-
raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by #{inspect} but has already been received by #{invoked_instance}"
-
end
-
end
-
-
1
if Support::RubyFeatures.module_prepends_supported?
-
1
def allow_no_prepended_module_definition_of(method_name)
-
prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass)
-
problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) }
-
return unless problem_mod
-
-
raise RSpec::Mocks::MockExpectationError,
-
"Using `any_instance` to stub a method (#{method_name}) that has been " \
-
"defined on a prepended module (#{problem_mod}) is not supported."
-
end
-
else
-
def allow_no_prepended_module_definition_of(_method_name)
-
# nothing to do; prepends aren't supported on this version of ruby
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
1
module AnyInstance
-
# @private
-
1
class StubChain < Chain
-
# @private
-
1
def expectation_fulfilled?
-
true
-
end
-
-
1
private
-
-
1
def create_message_expectation_on(instance)
-
proxy = ::RSpec::Mocks.space.proxy_for(instance)
-
method_name, opts = @expectation_args
-
opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE)
-
-
stub = proxy.add_stub(method_name, opts, &@expectation_block)
-
@recorder.stubs[stub.message] << stub
-
-
if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks?
-
stub.and_yield_receiver_to_implementation
-
end
-
-
stub
-
end
-
-
1
def invocation_order
-
@invocation_order ||= {
-
:with => [nil],
-
:and_return => [:with, nil],
-
:and_raise => [:with, nil],
-
:and_yield => [:with, nil],
-
:and_call_original => [:with, nil]
-
}
-
end
-
-
1
def verify_invocation_order(rspec_method_name, *_args, &_block)
-
return if invocation_order[rspec_method_name].include?(last_message)
-
raise NoMethodError, "Undefined method #{rspec_method_name}"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
1
module AnyInstance
-
# @private
-
1
class StubChainChain < StubChain
-
1
def initialize(*args)
-
super
-
@expectation_fulfilled = false
-
end
-
-
1
private
-
-
1
def create_message_expectation_on(instance)
-
::RSpec::Mocks::StubChain.stub_chain_on(instance, *@expectation_args, &@expectation_block)
-
end
-
-
1
def invocation_order
-
@invocation_order ||= {
-
:and_return => [nil],
-
:and_raise => [nil],
-
:and_yield => [nil]
-
}
-
end
-
end
-
end
-
end
-
end
-
# We intentionally do not use the `RSpec::Support.require...` methods
-
# here so that this file can be loaded individually, as documented
-
# below.
-
1
require 'rspec/mocks/argument_matchers'
-
1
require 'rspec/support/fuzzy_matcher'
-
-
1
module RSpec
-
1
module Mocks
-
# Wrapper for matching arguments against a list of expected values. Used by
-
# the `with` method on a `MessageExpectation`:
-
#
-
# expect(object).to receive(:message).with(:a, 'b', 3)
-
# object.message(:a, 'b', 3)
-
#
-
# Values passed to `with` can be literal values or argument matchers that
-
# match against the real objects .e.g.
-
#
-
# expect(object).to receive(:message).with(hash_including(:a => 'b'))
-
#
-
# Can also be used directly to match the contents of any `Array`. This
-
# enables 3rd party mocking libs to take advantage of rspec's argument
-
# matching without using the rest of rspec-mocks.
-
#
-
# require 'rspec/mocks/argument_list_matcher'
-
# include RSpec::Mocks::ArgumentMatchers
-
#
-
# arg_list_matcher = RSpec::Mocks::ArgumentListMatcher.new(123, hash_including(:a => 'b'))
-
# arg_list_matcher.args_match?(123, :a => 'b')
-
#
-
# This class is immutable.
-
#
-
# @see ArgumentMatchers
-
1
class ArgumentListMatcher
-
# @private
-
1
attr_reader :expected_args
-
-
# @api public
-
# @param [Array] expected_args a list of expected literals and/or argument matchers
-
#
-
# Initializes an `ArgumentListMatcher` with a collection of literal
-
# values and/or argument matchers.
-
#
-
# @see ArgumentMatchers
-
# @see #args_match?
-
1
def initialize(*expected_args)
-
1
@expected_args = expected_args
-
-
1
@matchers = case expected_args.first
-
1
when ArgumentMatchers::AnyArgsMatcher then Array
-
when ArgumentMatchers::NoArgsMatcher then []
-
else expected_args
-
end
-
end
-
-
# @api public
-
# @param [Array] args
-
#
-
# Matches each element in the `expected_args` against the element in the same
-
# position of the arguments passed to `new`.
-
#
-
# @see #initialize
-
1
def args_match?(*args)
-
4
Support::FuzzyMatcher.values_match?(@matchers, args)
-
end
-
-
# Value that will match all argument lists.
-
#
-
# @private
-
1
MATCH_ALL = new(ArgumentMatchers::AnyArgsMatcher.new)
-
end
-
end
-
end
-
# This cannot take advantage of our relative requires, since this file is a
-
# dependency of `rspec/mocks/argument_list_matcher.rb`. See comment there for
-
# details.
-
1
require 'rspec/support/matcher_definition'
-
-
1
module RSpec
-
1
module Mocks
-
# ArgumentMatchers are placeholders that you can include in message
-
# expectations to match arguments against a broader check than simple
-
# equality.
-
#
-
# With the exception of `any_args` and `no_args`, they all match against
-
# the arg in same position in the argument list.
-
#
-
# @see ArgumentListMatcher
-
1
module ArgumentMatchers
-
# Matches any args at all. Supports a more explicit variation of
-
# `expect(object).to receive(:message)`
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(any_args)
-
1
def any_args
-
AnyArgsMatcher.new
-
end
-
-
# Matches any argument at all.
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(anything)
-
1
def anything
-
AnyArgMatcher.new
-
end
-
-
# Matches no arguments.
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(no_args)
-
1
def no_args
-
NoArgsMatcher.new
-
end
-
-
# Matches if the actual argument responds to the specified messages.
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(duck_type(:hello))
-
# expect(object).to receive(:message).with(duck_type(:hello, :goodbye))
-
1
def duck_type(*args)
-
DuckTypeMatcher.new(*args)
-
end
-
-
# Matches a boolean value.
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(boolean())
-
1
def boolean
-
BooleanMatcher.new
-
end
-
-
# Matches a hash that includes the specified key(s) or key/value pairs.
-
# Ignores any additional keys.
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(hash_including(:key => val))
-
# expect(object).to receive(:message).with(hash_including(:key))
-
# expect(object).to receive(:message).with(hash_including(:key, :key2 => val2))
-
1
def hash_including(*args)
-
HashIncludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args))
-
end
-
-
# Matches an array that includes the specified items at least once.
-
# Ignores duplicates and additional values
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(array_including(1,2,3))
-
# expect(object).to receive(:message).with(array_including([1,2,3]))
-
1
def array_including(*args)
-
actually_an_array = Array === args.first && args.count == 1 ? args.first : args
-
ArrayIncludingMatcher.new(actually_an_array)
-
end
-
-
# Matches a hash that doesn't include the specified key(s) or key/value.
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(hash_excluding(:key => val))
-
# expect(object).to receive(:message).with(hash_excluding(:key))
-
# expect(object).to receive(:message).with(hash_excluding(:key, :key2 => :val2))
-
1
def hash_excluding(*args)
-
HashExcludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args))
-
end
-
-
1
alias_method :hash_not_including, :hash_excluding
-
-
# Matches if `arg.instance_of?(klass)`
-
#
-
# @example
-
#
-
# expect(object).to receive(:message).with(instance_of(Thing))
-
1
def instance_of(klass)
-
InstanceOf.new(klass)
-
end
-
-
1
alias_method :an_instance_of, :instance_of
-
-
# Matches if `arg.kind_of?(klass)`
-
# @example
-
#
-
# expect(object).to receive(:message).with(kind_of(Thing))
-
1
def kind_of(klass)
-
KindOf.new(klass)
-
end
-
-
1
alias_method :a_kind_of, :kind_of
-
-
# @private
-
1
def self.anythingize_lonely_keys(*args)
-
hash = args.last.class == Hash ? args.delete_at(-1) : {}
-
args.each { | arg | hash[arg] = AnyArgMatcher.new }
-
hash
-
end
-
-
# @private
-
1
class AnyArgsMatcher
-
1
def description
-
"any args"
-
end
-
end
-
-
# @private
-
1
class AnyArgMatcher
-
1
def ===(_other)
-
true
-
end
-
-
1
def description
-
"anything"
-
end
-
end
-
-
# @private
-
1
class NoArgsMatcher
-
1
def description
-
"no args"
-
end
-
end
-
-
# @private
-
1
class BooleanMatcher
-
1
def ===(value)
-
true == value || false == value
-
end
-
-
1
def description
-
"boolean"
-
end
-
end
-
-
# @private
-
1
class BaseHashMatcher
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
1
def ===(predicate, actual)
-
@expected.__send__(predicate) do |k, v|
-
actual.key?(k) && Support::FuzzyMatcher.values_match?(v, actual[k])
-
end
-
rescue NoMethodError
-
false
-
end
-
-
1
def description(name)
-
"#{name}(#{@expected.inspect.sub(/^\{/, "").sub(/\}$/, "")})"
-
end
-
end
-
-
# @private
-
1
class HashIncludingMatcher < BaseHashMatcher
-
1
def ===(actual)
-
super(:all?, actual)
-
end
-
-
1
def description
-
super("hash_including")
-
end
-
end
-
-
# @private
-
1
class HashExcludingMatcher < BaseHashMatcher
-
1
def ===(actual)
-
super(:none?, actual)
-
end
-
-
1
def description
-
super("hash_not_including")
-
end
-
end
-
-
# @private
-
1
class ArrayIncludingMatcher
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
1
def ===(actual)
-
Set.new(actual).superset?(Set.new(@expected))
-
end
-
-
1
def description
-
"array_including(#{@expected.join(", ")})"
-
end
-
end
-
-
# @private
-
1
class DuckTypeMatcher
-
1
def initialize(*methods_to_respond_to)
-
@methods_to_respond_to = methods_to_respond_to
-
end
-
-
1
def ===(value)
-
@methods_to_respond_to.all? { |message| value.respond_to?(message) }
-
end
-
-
1
def description
-
"duck_type(#{@methods_to_respond_to.map(&:inspect).join(', ')})"
-
end
-
end
-
-
# @private
-
1
class InstanceOf
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
1
def ===(actual)
-
actual.instance_of?(@klass)
-
end
-
-
1
def description
-
"an_instance_of(#{@klass.name})"
-
end
-
end
-
-
# @private
-
1
class KindOf
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
1
def ===(actual)
-
actual.kind_of?(@klass)
-
end
-
-
1
def description
-
"kind of #{@klass.name}"
-
end
-
end
-
-
1
matcher_namespace = name + '::'
-
1
::RSpec::Support.register_matcher_definition do |object|
-
# This is the best we have for now. We should tag all of our matchers
-
# with a module or something so we can test for it directly.
-
#
-
# (Note Module#parent in ActiveSupport is defined in a similar way.)
-
begin
-
object.class.name.include?(matcher_namespace)
-
rescue NoMethodError
-
# Some objects, like BasicObject, don't implemented standard
-
# reflection methods.
-
false
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Provides configuration options for rspec-mocks.
-
1
class Configuration
-
1
def initialize
-
1
@yield_receiver_to_any_instance_implementation_blocks = true
-
1
@verify_doubled_constant_names = false
-
1
@transfer_nested_constants = false
-
1
@verify_partial_doubles = false
-
end
-
-
1
def yield_receiver_to_any_instance_implementation_blocks?
-
@yield_receiver_to_any_instance_implementation_blocks
-
end
-
-
# Sets whether or not RSpec will yield the receiving instance of a
-
# message to blocks that are used for any_instance stub implementations.
-
# When set, the first yielded argument will be the receiving instance.
-
# Defaults to `true`.
-
#
-
# @example
-
#
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.yield_receiver_to_any_instance_implementation_blocks = false
-
# end
-
# end
-
1
attr_writer :yield_receiver_to_any_instance_implementation_blocks
-
-
# Adds `stub` and `should_receive` to the given
-
# modules or classes. This is usually only necessary
-
# if you application uses some proxy classes that
-
# "strip themselves down" to a bare minimum set of
-
# methods and remove `stub` and `should_receive` in
-
# the process.
-
#
-
# @example
-
#
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.add_stub_and_should_receive_to Delegator
-
# end
-
# end
-
#
-
1
def add_stub_and_should_receive_to(*modules)
-
modules.each do |mod|
-
Syntax.enable_should(mod)
-
end
-
end
-
-
# Provides the ability to set either `expect`,
-
# `should` or both syntaxes. RSpec uses `expect`
-
# syntax by default. This is needed if you want to
-
# explicitly enable `should` syntax and/or explicitly
-
# disable `expect` syntax.
-
#
-
# @example
-
#
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.syntax = [:expect, :should]
-
# end
-
# end
-
#
-
1
def syntax=(*values)
-
1
syntaxes = values.flatten
-
1
if syntaxes.include?(:expect)
-
1
Syntax.enable_expect
-
else
-
Syntax.disable_expect
-
end
-
-
1
if syntaxes.include?(:should)
-
1
Syntax.enable_should
-
else
-
Syntax.disable_should
-
end
-
end
-
-
# Returns an array with a list of syntaxes
-
# that are enabled.
-
#
-
# @example
-
#
-
# unless RSpec::Mocks.configuration.syntax.include?(:expect)
-
# raise "this RSpec extension gem requires the rspec-mocks `:expect` syntax"
-
# end
-
#
-
1
def syntax
-
syntaxes = []
-
syntaxes << :should if Syntax.should_enabled?
-
syntaxes << :expect if Syntax.expect_enabled?
-
syntaxes
-
end
-
-
1
def verify_doubled_constant_names?
-
!!@verify_doubled_constant_names
-
end
-
-
# When this is set to true, an error will be raised when
-
# `instance_double` or `class_double` is given the name of an undefined
-
# constant. You probably only want to set this when running your entire
-
# test suite, with all production code loaded. Setting this for an
-
# isolated unit test will prevent you from being able to isolate it!
-
1
attr_writer :verify_doubled_constant_names
-
-
1
def transfer_nested_constants?
-
!!@transfer_nested_constants
-
end
-
-
# Sets the default for the `transfer_nested_constants` option when
-
# stubbing constants.
-
1
attr_writer :transfer_nested_constants
-
-
# When set to true, partial mocks will be verified the same as object
-
# doubles. Any stubs will have their arguments checked against the original
-
# method, and methods that do not exist cannot be stubbed.
-
1
def verify_partial_doubles=(val)
-
@verify_partial_doubles = !!val
-
end
-
-
1
def verify_partial_doubles?
-
4
@verify_partial_doubles
-
end
-
-
# Monkey-patch `Marshal.dump` to enable dumping of mocked or stubbed
-
# objects. By default this will not work since RSpec mocks works by
-
# adding singleton methods that cannot be serialized. This patch removes
-
# these singleton methods before serialization. Setting to falsey removes
-
# the patch.
-
#
-
# This method is idempotent.
-
1
def patch_marshal_to_support_partial_doubles=(val)
-
if val
-
RSpec::Mocks::MarshalExtension.patch!
-
else
-
RSpec::Mocks::MarshalExtension.unpatch!
-
end
-
end
-
-
# @api private
-
# Resets the configured syntax to the default.
-
1
def reset_syntaxes_to_default
-
1
self.syntax = [:should, :expect]
-
1
RSpec::Mocks::Syntax.warn_about_should!
-
end
-
end
-
-
# Mocks specific configuration, as distinct from `RSpec.configuration`
-
# which is core RSpec configuration.
-
1
def self.configuration
-
5
@configuration ||= Configuration.new
-
end
-
-
1
configuration.reset_syntaxes_to_default
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Raised when a message expectation is not satisfied.
-
1
MockExpectationError = Class.new(Exception)
-
-
# Raised when a test double is used after it has been torn
-
# down (typically at the end of an rspec-core example).
-
1
ExpiredTestDoubleError = Class.new(MockExpectationError)
-
-
# Raised when doubles or partial doubles are used outside of the per-test lifecycle.
-
1
OutsideOfExampleError = Class.new(StandardError)
-
-
# @private
-
1
UnsupportedMatcherError = Class.new(StandardError)
-
# @private
-
1
NegationUnsupportedError = Class.new(StandardError)
-
# @private
-
1
VerifyingDoubleNotDefinedError = Class.new(StandardError)
-
-
# @private
-
1
class ErrorGenerator
-
1
attr_writer :opts
-
-
1
def initialize(target, name)
-
4
@target = target
-
4
@name = name
-
end
-
-
# @private
-
1
def opts
-
@opts ||= {}
-
end
-
-
# @private
-
1
def raise_unexpected_message_error(message, *args)
-
__raise "#{intro} received unexpected message :#{message}#{arg_message(*args)}"
-
end
-
-
# @private
-
1
def raise_unexpected_message_args_error(expectation, *args)
-
expected_args = format_args(*expectation.expected_args)
-
actual_args = format_received_args(*args)
-
__raise "#{intro} received #{expectation.message.inspect} with " \
-
"unexpected arguments\n expected: #{expected_args}\n" \
-
" got: #{actual_args}"
-
end
-
-
# @private
-
1
def raise_missing_default_stub_error(expectation, *args)
-
expected_args = format_args(*expectation.expected_args)
-
actual_args = format_received_args(*args)
-
__raise "#{intro} received #{expectation.message.inspect} with " \
-
"unexpected arguments\n expected: #{expected_args}\n" \
-
" got: #{actual_args}\n Please stub a default value " \
-
"first if message might be received with other args as well. \n"
-
end
-
-
# @private
-
1
def raise_similar_message_args_error(expectation, *args_for_multiple_calls)
-
expected_args = format_args(*expectation.expected_args)
-
actual_args = args_for_multiple_calls.map { |a| format_received_args(*a) }.join(", ")
-
__raise "#{intro} received #{expectation.message.inspect} with " \
-
"unexpected arguments\n expected: #{expected_args}\n" \
-
" got: #{actual_args}"
-
end
-
-
# rubocop:disable Style/ParameterLists
-
# @private
-
1
def raise_expectation_error(message, expected_received_count, argument_list_matcher, actual_received_count, expectation_count_type, *args)
-
expected_part = expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
-
received_part = received_part_of_expectation_error(actual_received_count, *args)
-
__raise "(#{intro}).#{message}#{format_args(*args)}\n #{expected_part}\n #{received_part}"
-
end
-
# rubocop:enable Style/ParameterLists
-
-
# @private
-
1
def raise_unimplemented_error(doubled_module, method_name)
-
__raise "%s does not implement: %s" % [
-
doubled_module.description,
-
method_name
-
]
-
end
-
-
# @private
-
1
def raise_non_public_error(method_name, visibility)
-
raise NoMethodError, "%s method `%s' called on %s" % [
-
visibility, method_name, intro
-
]
-
end
-
-
# @private
-
1
def raise_invalid_arguments_error(verifier)
-
__raise verifier.error_message
-
end
-
-
# @private
-
1
def raise_expired_test_double_error
-
raise ExpiredTestDoubleError,
-
"#{intro} was originally created in one example but has leaked into " \
-
"another example and can no longer be used. rspec-mocks' doubles are " \
-
"designed to only last for one example, and you need to create a new " \
-
"one in each example you wish to use it for."
-
end
-
-
# @private
-
1
def received_part_of_expectation_error(actual_received_count, *args)
-
"received: #{count_message(actual_received_count)}" +
-
actual_method_call_args_description(actual_received_count, args)
-
end
-
-
# @private
-
1
def expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
-
"expected: #{count_message(expected_received_count, expectation_count_type)}" +
-
expected_method_call_args_description(argument_list_matcher.expected_args)
-
end
-
-
# @private
-
1
def actual_method_call_args_description(count, args)
-
method_call_args_description(args) ||
-
if count > 0 && args.length > 0
-
" with arguments: #{args.inspect.gsub(/\A\[(.+)\]\z/, '(\1)')}"
-
else
-
""
-
end
-
end
-
-
# @private
-
1
def expected_method_call_args_description(args)
-
method_call_args_description(args) ||
-
if args.length > 0
-
" with arguments: #{format_args(*args)}"
-
else
-
""
-
end
-
end
-
-
# @private
-
1
def method_call_args_description(args)
-
case args.first
-
when ArgumentMatchers::AnyArgsMatcher then " with any arguments"
-
when ArgumentMatchers::NoArgsMatcher then " with no arguments"
-
end
-
end
-
-
# @private
-
1
def describe_expectation(message, expected_received_count, _actual_received_count, *args)
-
"have received #{message}#{format_args(*args)} #{count_message(expected_received_count)}"
-
end
-
-
# @private
-
1
def raise_out_of_order_error(message)
-
__raise "#{intro} received :#{message} out of order"
-
end
-
-
# @private
-
1
def raise_block_failed_error(message, detail)
-
__raise "#{intro} received :#{message} but passed block failed with: #{detail}"
-
end
-
-
# @private
-
1
def raise_missing_block_error(args_to_yield)
-
__raise "#{intro} asked to yield |#{arg_list(*args_to_yield)}| but no block was passed"
-
end
-
-
# @private
-
1
def raise_wrong_arity_error(args_to_yield, signature)
-
__raise "#{intro} yielded |#{arg_list(*args_to_yield)}| to block with #{signature.description}"
-
end
-
-
# @private
-
1
def raise_only_valid_on_a_partial_double(method)
-
__raise "#{intro} is a pure test double. `#{method}` is only " \
-
"available on a partial double."
-
end
-
-
# @private
-
1
def raise_expectation_on_unstubbed_method(method)
-
__raise "#{intro} expected to have received #{method}, but that " \
-
"object is not a spy or method has not been stubbed."
-
end
-
-
# @private
-
1
def raise_expectation_on_mocked_method(method)
-
__raise "#{intro} expected to have received #{method}, but that " \
-
"method has been mocked instead of stubbed or spied."
-
end
-
-
1
def self.raise_double_negation_error(wrapped_expression)
-
raise "Isn't life confusing enough? You've already set a " \
-
"negative message expectation and now you are trying to " \
-
"negate it again with `never`. What does an expression like " \
-
"`#{wrapped_expression}.not_to receive(:msg).never` even mean?"
-
end
-
-
1
private
-
-
1
def intro
-
if @name
-
"Double #{@name.inspect}"
-
elsif TestDouble === @target
-
"Double"
-
elsif Class === @target
-
"<#{@target.inspect} (class)>"
-
elsif @target
-
@target
-
else
-
"nil"
-
end
-
end
-
-
1
def __raise(message)
-
message = opts[:message] unless opts[:message].nil?
-
Kernel.raise(RSpec::Mocks::MockExpectationError, message)
-
end
-
-
1
def arg_message(*args)
-
" with " + format_args(*args)
-
end
-
-
1
def format_args(*args)
-
args.empty? ? "(no args)" : "(" + arg_list(*args) + ")"
-
end
-
-
1
def arg_list(*args)
-
args.map { |arg| arg_has_valid_description(arg) ? arg.description : arg.inspect }.join(", ")
-
end
-
-
1
def arg_has_valid_description(arg)
-
return false unless arg.respond_to?(:description)
-
-
!arg.description.nil? && !arg.description.empty?
-
end
-
-
1
def format_received_args(*args)
-
args.empty? ? "(no args)" : "(" + received_arg_list(*args) + ")"
-
end
-
-
1
def received_arg_list(*args)
-
args.map(&:inspect).join(", ")
-
end
-
-
1
def count_message(count, expectation_count_type=nil)
-
return "at least #{times(count.abs)}" if count < 0 || expectation_count_type == :at_least
-
return "at most #{times(count)}" if expectation_count_type == :at_most
-
times(count)
-
end
-
-
1
def times(count)
-
"#{count} time#{count == 1 ? '' : 's'}"
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'object_reference'
-
-
1
module RSpec
-
1
module Mocks
-
# Contains methods intended to be used from within code examples.
-
# Mix this in to your test context (such as a test framework base class)
-
# to use rspec-mocks with your test framework. If you're using rspec-core,
-
# it'll take care of doing this for you.
-
1
module ExampleMethods
-
1
include RSpec::Mocks::ArgumentMatchers
-
-
# @overload double()
-
# @overload double(name)
-
# @param name [String/Symbol] used to clarify intent
-
# @overload double(stubs)
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @overload double(name, stubs)
-
# @param name [String/Symbol] used to clarify intent
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @return (Double)
-
#
-
# Constructs an instance of [RSpec::Mocks::Double](RSpec::Mocks::Double) configured
-
# with an optional name, used for reporting in failure messages, and an optional
-
# hash of message/return-value pairs.
-
#
-
# @example
-
#
-
# book = double("book", :title => "The RSpec Book")
-
# book.title #=> "The RSpec Book"
-
#
-
# card = double("card", :suit => "Spades", :rank => "A")
-
# card.suit #=> "Spades"
-
# card.rank #=> "A"
-
#
-
1
def double(*args)
-
ExampleMethods.declare_double(Double, *args)
-
end
-
-
# @overload instance_double(doubled_class)
-
# @param doubled_class [String, Class]
-
# @overload instance_double(doubled_class, stubs)
-
# @param doubled_class [String, Class]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return InstanceVerifyingDouble
-
#
-
# Constructs a test double against a specific class. If the given class
-
# name has been loaded, only instance methods defined on the class are
-
# allowed to be stubbed. In all other ways it behaves like a
-
# [double](double).
-
1
def instance_double(doubled_class, *args)
-
ref = ObjectReference.for(doubled_class)
-
ExampleMethods.declare_verifying_double(InstanceVerifyingDouble, ref, *args)
-
end
-
-
# @overload class_double(doubled_class)
-
# @param doubled_class [String, Module]
-
# @overload class_double(doubled_class, stubs)
-
# @param doubled_class [String, Module]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ClassVerifyingDouble
-
#
-
# Constructs a test double against a specific class. If the given class
-
# name has been loaded, only class methods defined on the class are
-
# allowed to be stubbed. In all other ways it behaves like a
-
# [double](double).
-
1
def class_double(doubled_class, *args)
-
ref = ObjectReference.for(doubled_class)
-
ExampleMethods.declare_verifying_double(ClassVerifyingDouble, ref, *args)
-
end
-
-
# @overload object_double(object_or_name)
-
# @param object_or_name [String, Object]
-
# @overload object_double(object_or_name, stubs)
-
# @param object_or_name [String, Object]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ObjectVerifyingDouble
-
#
-
# Constructs a test double against a specific object. Only the methods
-
# the object responds to are allowed to be stubbed. If a String argument
-
# is provided, it is assumed to reference a constant object which is used
-
# for verification. In all other ways it behaves like a [double](double).
-
1
def object_double(object_or_name, *args)
-
ref = ObjectReference.for(object_or_name, :allow_direct_object_refs)
-
ExampleMethods.declare_verifying_double(ObjectVerifyingDouble, ref, *args)
-
end
-
-
# @overload spy()
-
# @overload spy(name)
-
# @param name [String/Symbol] used to clarify intent
-
# @overload spy(stubs)
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @overload spy(name, stubs)
-
# @param name [String/Symbol] used to clarify intent
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @return (Double)
-
#
-
# Constructs a test double that is optimized for use with
-
# `have_received`. With a normal double one has to stub methods in order
-
# to be able to spy them. A spy automatically spies on all methods.
-
1
def spy(*args)
-
double(*args).as_null_object
-
end
-
-
# @overload instance_spy(doubled_class)
-
# @param doubled_class [String, Class]
-
# @overload instance_spy(doubled_class, stubs)
-
# @param doubled_class [String, Class]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return InstanceVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific class. If the given class name has been loaded, only
-
# instance methods defined on the class are allowed to be stubbed. With
-
# a normal double one has to stub methods in order to be able to spy
-
# them. An instance_spy automatically spies on all instance methods to
-
# which the class responds.
-
1
def instance_spy(*args)
-
instance_double(*args).as_null_object
-
end
-
-
# @overload object_spy(object_or_name)
-
# @param object_or_name [String, Object]
-
# @overload object_spy(object_or_name, stubs)
-
# @param object_or_name [String, Object]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ObjectVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific object. Only instance methods defined on the object
-
# are allowed to be stubbed. With a normal double one has to stub
-
# methods in order to be able to spy them. An object_spy automatically
-
# spies on all methods to which the object responds.
-
1
def object_spy(*args)
-
object_double(*args).as_null_object
-
end
-
-
# @overload class_spy(doubled_class)
-
# @param doubled_class [String, Module]
-
# @overload class_spy(doubled_class, stubs)
-
# @param doubled_class [String, Module]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ClassVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific class. If the given class name has been loaded,
-
# only class methods defined on the class are allowed to be stubbed.
-
# With a normal double one has to stub methods in order to be able to spy
-
# them. An class_spy automatically spies on all class methods to which the
-
# class responds.
-
1
def class_spy(*args)
-
class_double(*args).as_null_object
-
end
-
-
# Disables warning messages about expectations being set on nil.
-
#
-
# By default warning messages are issued when expectations are set on
-
# nil. This is to prevent false-positives and to catch potential bugs
-
# early on.
-
1
def allow_message_expectations_on_nil
-
RSpec::Mocks.space.proxy_for(nil).warn_about_expectations = false
-
end
-
-
# Stubs the named constant with the given value.
-
# Like method stubs, the constant will be restored
-
# to its original value (or lack of one, if it was
-
# undefined) when the example completes.
-
#
-
# @param constant_name [String] The fully qualified name of the constant. The current
-
# constant scoping at the point of call is not considered.
-
# @param value [Object] The value to make the constant refer to. When the
-
# example completes, the constant will be restored to its prior state.
-
# @param options [Hash] Stubbing options.
-
# @option options :transfer_nested_constants [Boolean, Array<Symbol>] Determines
-
# what nested constants, if any, will be transferred from the original value
-
# of the constant to the new value of the constant. This only works if both
-
# the original and new values are modules (or classes).
-
# @return [Object] the stubbed value of the constant
-
#
-
# @example
-
#
-
# stub_const("MyClass", Class.new) # => Replaces (or defines) MyClass with a new class object.
-
# stub_const("SomeModel::PER_PAGE", 5) # => Sets SomeModel::PER_PAGE to 5.
-
#
-
# class CardDeck
-
# SUITS = [:Spades, :Diamonds, :Clubs, :Hearts]
-
# NUM_CARDS = 52
-
# end
-
#
-
# stub_const("CardDeck", Class.new)
-
# CardDeck::SUITS # => uninitialized constant error
-
# CardDeck::NUM_CARDS # => uninitialized constant error
-
#
-
# stub_const("CardDeck", Class.new, :transfer_nested_constants => true)
-
# CardDeck::SUITS # => our suits array
-
# CardDeck::NUM_CARDS # => 52
-
#
-
# stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS])
-
# CardDeck::SUITS # => our suits array
-
# CardDeck::NUM_CARDS # => uninitialized constant error
-
1
def stub_const(constant_name, value, options={})
-
ConstantMutator.stub(constant_name, value, options)
-
end
-
-
# Hides the named constant with the given value. The constant will be
-
# undefined for the duration of the test.
-
#
-
# Like method stubs, the constant will be restored to its original value
-
# when the example completes.
-
#
-
# @param constant_name [String] The fully qualified name of the constant.
-
# The current constant scoping at the point of call is not considered.
-
#
-
# @example
-
#
-
# hide_const("MyClass") # => MyClass is now an undefined constant
-
1
def hide_const(constant_name)
-
ConstantMutator.hide(constant_name)
-
end
-
-
# Verifies that the given object received the expected message during the
-
# course of the test. On a spy objects or as null object doubles this
-
# works for any method, on other objects the method must have
-
# been stubbed beforehand in order for messages to be verified.
-
#
-
# Stubbing and verifying messages received in this way implements the
-
# Test Spy pattern.
-
#
-
# @param method_name [Symbol] name of the method expected to have been
-
# called.
-
#
-
# @example
-
#
-
# invitation = double('invitation', accept: true)
-
# user.accept_invitation(invitation)
-
# expect(invitation).to have_received(:accept)
-
#
-
# # You can also use most message expectations:
-
# expect(invitation).to have_received(:accept).with(mailer).once
-
1
def have_received(method_name, &block)
-
Matchers::HaveReceived.new(method_name, &block)
-
end
-
-
# @method expect
-
# Used to wrap an object in preparation for setting a mock expectation
-
# on it.
-
#
-
# @example
-
#
-
# expect(obj).to receive(:foo).with(5).and_return(:return_value)
-
#
-
# @note This method is usually provided by rspec-expectations. However,
-
# if you use rspec-mocks without rspec-expectations, there's a definition
-
# of it that is made available here. If you disable the `:expect` syntax
-
# this method will be undefined.
-
-
# @method allow
-
# Used to wrap an object in preparation for stubbing a method
-
# on it.
-
#
-
# @example
-
#
-
# allow(dbl).to receive(:foo).with(5).and_return(:return_value)
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method expect_any_instance_of
-
# Used to wrap a class in preparation for setting a mock expectation
-
# on instances of it.
-
#
-
# @example
-
#
-
# expect_any_instance_of(MyClass).to receive(:foo)
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method allow_any_instance_of
-
# Used to wrap a class in preparation for stubbing a method
-
# on instances of it.
-
#
-
# @example
-
#
-
# allow_any_instance_of(MyClass).to receive(:foo)
-
#
-
# @note This is only available when you have enabled the `expect` syntax.
-
-
# @method receive
-
# Used to specify a message that you expect or allow an object
-
# to receive. The object returned by `receive` supports the same
-
# fluent interface that `should_receive` and `stub` have always
-
# supported, allowing you to constrain the arguments or number of
-
# times, and configure how the object should respond to the message.
-
#
-
# @example
-
#
-
# expect(obj).to receive(:hello).with("world").exactly(3).times
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method receive_messages
-
# Shorthand syntax used to setup message(s), and their return value(s),
-
# that you expect or allow an object to receive. The method takes a hash
-
# of messages and their respective return values. Unlike with `receive`,
-
# you cannot apply further customizations using a block or the fluent
-
# interface.
-
#
-
# @example
-
#
-
# allow(obj).to receive_messages(:speak => "Hello World")
-
# allow(obj).to receive_messages(:speak => "Hello", :meow => "Meow")
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method receive_message_chain
-
# @overload receive_message_chain(method1, method2)
-
# @overload receive_message_chain("method1.method2")
-
# @overload receive_message_chain(method1, method_to_value_hash)
-
#
-
# stubs/mocks a chain of messages on an object or test double.
-
#
-
# ## Warning:
-
#
-
# Chains can be arbitrarily long, which makes it quite painless to
-
# violate the Law of Demeter in violent ways, so you should consider any
-
# use of `receive_message_chain` a code smell. Even though not all code smells
-
# indicate real problems (think fluent interfaces), `receive_message_chain` still
-
# results in brittle examples. For example, if you write
-
# `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the
-
# implementation calls `foo.baz.bar`, the stub will not work.
-
#
-
# @example
-
#
-
# allow(double).to receive_message_chain("foo.bar") { :baz }
-
# allow(double).to receive_message_chain(:foo, :bar => :baz)
-
# allow(double).to receive_message_chain(:foo, :bar) { :baz }
-
#
-
# # Given any of ^^ these three forms ^^:
-
# double.foo.bar # => :baz
-
#
-
# # Common use in Rails/ActiveRecord:
-
# allow(Article).to receive_message_chain("recent.published") { [Article.new] }
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @private
-
1
def self.included(klass)
-
1
klass.class_exec do
-
# This gets mixed in so that if `RSpec::Matchers` is included in
-
# `klass` later, it's definition of `expect` will take precedence.
-
1
include ExpectHost unless method_defined?(:expect)
-
end
-
end
-
-
# @private
-
1
def self.declare_verifying_double(type, ref, *args)
-
if RSpec::Mocks.configuration.verify_doubled_constant_names? &&
-
!ref.defined?
-
-
raise VerifyingDoubleNotDefinedError,
-
"#{ref.description} is not a defined constant. " \
-
"Perhaps you misspelt it? " \
-
"Disable check with verify_doubled_constant_names configuration option."
-
end
-
-
declare_double(type, ref, *args)
-
end
-
-
# @private
-
1
def self.declare_double(type, *args)
-
args << {} unless Hash === args.last
-
type.new(*args)
-
end
-
-
# This module exists to host the `expect` method for cases where
-
# rspec-mocks is used w/o rspec-expectations.
-
1
module ExpectHost
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class InstanceMethodStasher
-
1
def initialize(object, method)
-
4
@object = object
-
4
@method = method
-
8
@klass = (class << object; self; end)
-
-
4
@original_method = nil
-
4
@method_is_stashed = false
-
end
-
-
1
attr_reader :original_method
-
-
1
if RUBY_VERSION.to_f < 1.9
-
# @private
-
def method_is_stashed?
-
@method_is_stashed
-
end
-
-
# @private
-
def stash
-
return if !method_defined_directly_on_klass? || @method_is_stashed
-
-
@klass.__send__(:alias_method, stashed_method_name, @method)
-
@method_is_stashed = true
-
end
-
-
# @private
-
def stashed_method_name
-
"obfuscated_by_rspec_mocks__#{@method}"
-
end
-
private :stashed_method_name
-
-
# @private
-
def restore
-
return unless @method_is_stashed
-
-
if @klass.__send__(:method_defined?, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
@klass.__send__(:alias_method, @method, stashed_method_name)
-
@klass.__send__(:remove_method, stashed_method_name)
-
@method_is_stashed = false
-
end
-
else
-
-
# @private
-
1
def method_is_stashed?
-
4
!!@original_method
-
end
-
-
# @private
-
1
def stash
-
4
return unless method_defined_directly_on_klass?
-
@original_method ||= ::RSpec::Support.method_handle_for(@object, @method)
-
end
-
-
# @private
-
1
def restore
-
return unless @original_method
-
-
if @klass.__send__(:method_defined?, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
-
handle_restoration_failures do
-
@klass.__send__(:define_method, @method, @original_method)
-
end
-
-
@original_method = nil
-
end
-
end
-
-
1
if RUBY_DESCRIPTION.include?('2.0.0p247') || RUBY_DESCRIPTION.include?('2.0.0p195')
-
# ruby 2.0.0-p247 and 2.0.0-p195 both have a bug that we can't work around :(.
-
# https://bugs.ruby-lang.org/issues/8686
-
def handle_restoration_failures
-
yield
-
rescue TypeError
-
RSpec.warn_with(
-
"RSpec failed to properly restore a partial double (#{@object.inspect}) " \
-
"to its original state due to a known bug in MRI 2.0.0-p195 & p247 " \
-
"(https://bugs.ruby-lang.org/issues/8686). This object may remain " \
-
"screwed up for the rest of this process. Please upgrade to 2.0.0-p353 or above.",
-
:call_site => nil, :use_spec_location_as_call_site => true
-
)
-
end
-
else
-
1
def handle_restoration_failures
-
# No known reasons for restoration to fail on other rubies.
-
yield
-
end
-
end
-
-
1
private
-
-
# @private
-
1
def method_defined_directly_on_klass?
-
4
method_defined_on_klass? && method_owned_by_klass?
-
end
-
-
# @private
-
1
def method_defined_on_klass?(klass=@klass)
-
8
MethodReference.method_defined_at_any_visibility?(klass, @method)
-
end
-
-
1
def method_owned_by_klass?
-
4
owner = @klass.instance_method(@method).owner
-
-
# On Ruby 2.0.0+ the owner of a method on a class which has been
-
# `prepend`ed may actually be an instance, e.g.
-
# `#<MyClass:0x007fbb94e3cd10>`, rather than the expected `MyClass`.
-
4
owner = owner.class unless Module === owner
-
-
# On some 1.9s (e.g. rubinius) aliased methods
-
# can report the wrong owner. Example:
-
# class MyClass
-
# class << self
-
# alias alternate_new new
-
# end
-
# end
-
#
-
# MyClass.owner(:alternate_new) returns `Class` when incorrect,
-
# but we need to consider the owner to be `MyClass` because
-
# it is not actually available on `Class` but is on `MyClass`.
-
# Hence, we verify that the owner actually has the method defined.
-
# If the given owner does not have the method defined, we assume
-
# that the method is actually owned by @klass.
-
4
owner == @klass || !(method_defined_on_klass?(owner))
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
1
module Matchers
-
# @private
-
1
class ExpectationCustomization
-
1
attr_accessor :block
-
-
1
def initialize(method_name, args, block)
-
4
@method_name = method_name
-
4
@args = args
-
4
@block = block
-
end
-
-
1
def playback_onto(expectation)
-
4
expectation.__send__(@method_name, *@args, &@block)
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'matchers/expectation_customization'
-
-
1
module RSpec
-
1
module Mocks
-
1
module Matchers
-
# @private
-
1
class Receive
-
1
def initialize(message, block)
-
4
@message = message
-
4
@block = block
-
4
@recorded_customizations = []
-
end
-
-
1
def name
-
"receive"
-
end
-
-
1
def setup_expectation(subject, &block)
-
warn_if_any_instance("expect", subject)
-
setup_mock_proxy_method_substitute(subject, :add_message_expectation, block)
-
end
-
1
alias matches? setup_expectation
-
-
1
def setup_negative_expectation(subject, &block)
-
# ensure `never` goes first for cases like `never.and_return(5)`,
-
# where `and_return` is meant to raise an error
-
@recorded_customizations.unshift ExpectationCustomization.new(:never, [], nil)
-
-
warn_if_any_instance("expect", subject)
-
-
setup_expectation(subject, &block)
-
end
-
1
alias does_not_match? setup_negative_expectation
-
-
1
def setup_allowance(subject, &block)
-
4
warn_if_any_instance("allow", subject)
-
4
setup_mock_proxy_method_substitute(subject, :add_stub, block)
-
end
-
-
1
def setup_any_instance_expectation(subject, &block)
-
setup_any_instance_method_substitute(subject, :should_receive, block)
-
end
-
-
1
def setup_any_instance_negative_expectation(subject, &block)
-
setup_any_instance_method_substitute(subject, :should_not_receive, block)
-
end
-
-
1
def setup_any_instance_allowance(subject, &block)
-
setup_any_instance_method_substitute(subject, :stub, block)
-
end
-
-
1
MessageExpectation.public_instance_methods(false).each do |method|
-
48
next if method_defined?(method)
-
-
47
define_method(method) do |*args, &block|
-
4
@recorded_customizations << ExpectationCustomization.new(method, args, block)
-
4
self
-
end
-
end
-
-
1
private
-
-
1
def warn_if_any_instance(expression, subject)
-
4
return unless AnyInstance::Proxy === subject
-
-
RSpec.warning(
-
"`#{expression}(#{subject.klass}.any_instance).to` " \
-
"is probably not what you meant, it does not operate on " \
-
"any instance of `#{subject.klass}`. " \
-
"Use `#{expression}_any_instance_of(#{subject.klass}).to` instead."
-
)
-
end
-
-
1
def setup_mock_proxy_method_substitute(subject, method, block)
-
4
proxy = ::RSpec::Mocks.space.proxy_for(subject)
-
4
setup_method_substitute(proxy, method, block)
-
end
-
-
1
def setup_any_instance_method_substitute(subject, method, block)
-
proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject)
-
setup_method_substitute(proxy, method, block)
-
end
-
-
1
def setup_method_substitute(host, method, block, *args)
-
4
args << @message.to_sym
-
4
block = move_block_to_last_customization(block)
-
-
4
expectation = host.__send__(method, *args, &(@block || block))
-
-
4
@recorded_customizations.each do |customization|
-
4
customization.playback_onto(expectation)
-
end
-
4
expectation
-
end
-
-
1
def move_block_to_last_customization(block)
-
4
last = @recorded_customizations.last
-
4
return block unless last
-
-
4
last.block ||= block
-
nil
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# A message expectation that only allows concrete return values to be set
-
# for a message. While this same effect can be achieved using a standard
-
# MessageExpecation, this version is much faster and so can be used as an
-
# optimization.
-
#
-
# @private
-
1
class SimpleMessageExpectation
-
1
def initialize(message, response, error_generator, backtrace_line=nil)
-
@message, @response, @error_generator, @backtrace_line = message.to_sym, response, error_generator, backtrace_line
-
@received = false
-
end
-
-
1
def invoke(*_)
-
@received = true
-
@response
-
end
-
-
1
def matches?(message, *_)
-
@message == message.to_sym
-
end
-
-
1
def called_max_times?
-
false
-
end
-
-
1
def verify_messages_received
-
InsertOntoBacktrace.line(@backtrace_line) do
-
unless @received
-
@error_generator.raise_expectation_error(@message, 1, ArgumentListMatcher::MATCH_ALL, 0, nil)
-
end
-
end
-
end
-
end
-
-
# @private
-
1
class MessageExpectation
-
# @private
-
1
attr_accessor :error_generator, :implementation
-
1
attr_reader :message
-
1
attr_reader :orig_object
-
1
attr_writer :expected_received_count, :expected_from, :argument_list_matcher
-
1
protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation=
-
-
# rubocop:disable Style/ParameterLists
-
# @private
-
1
def initialize(error_generator, expectation_ordering, expected_from, method_double,
-
type=:expectation, opts={}, &implementation_block)
-
4
@error_generator = error_generator
-
4
@error_generator.opts = opts
-
4
@expected_from = expected_from
-
4
@method_double = method_double
-
4
@orig_object = @method_double.object
-
4
@message = @method_double.method_name
-
4
@actual_received_count = 0
-
4
@expected_received_count = type == :expectation ? 1 : :any
-
4
@argument_list_matcher = ArgumentListMatcher::MATCH_ALL
-
4
@order_group = expectation_ordering
-
4
@order_group.register(self) unless type == :stub
-
4
@expectation_type = type
-
4
@ordered = false
-
4
@at_least = @at_most = @exactly = nil
-
4
@args_to_yield = []
-
4
@failed_fast = nil
-
4
@eval_context = nil
-
4
@yield_receiver_to_implementation_block = false
-
-
4
@implementation = Implementation.new
-
4
self.inner_implementation_action = implementation_block
-
end
-
# rubocop:enable Style/ParameterLists
-
-
# @private
-
1
def expected_args
-
@argument_list_matcher.expected_args
-
end
-
-
# @overload and_return(value)
-
# @overload and_return(first_value, second_value)
-
#
-
# Tells the object to return a value when it receives the message. Given
-
# more than one value, the first value is returned the first time the
-
# message is received, the second value is returned the next time, etc,
-
# etc.
-
#
-
# If the message is received more times than there are values, the last
-
# value is received for every subsequent call.
-
#
-
# @example
-
#
-
# allow(counter).to receive(:count).and_return(1)
-
# counter.count # => 1
-
# counter.count # => 1
-
#
-
# allow(counter).to receive(:count).and_return(1,2,3)
-
# counter.count # => 1
-
# counter.count # => 2
-
# counter.count # => 3
-
# counter.count # => 3
-
# counter.count # => 3
-
# # etc
-
1
def and_return(first_value, *values)
-
if negative?
-
raise "`and_return` is not supported with negative message expectations"
-
end
-
-
if block_given?
-
raise ArgumentError, "Implementation blocks aren't supported with `and_return`"
-
end
-
-
values.unshift(first_value)
-
@expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least)
-
self.terminal_implementation_action = AndReturnImplementation.new(values)
-
-
nil
-
end
-
-
1
def and_yield_receiver_to_implementation
-
@yield_receiver_to_implementation_block = true
-
self
-
end
-
-
1
def yield_receiver_to_implementation_block?
-
4
@yield_receiver_to_implementation_block
-
end
-
-
# Tells the object to delegate to the original unmodified method
-
# when it receives the message.
-
#
-
# @note This is only available on partial doubles.
-
#
-
# @example
-
#
-
# expect(counter).to receive(:increment).and_call_original
-
# original_count = counter.count
-
# counter.increment
-
# expect(counter.count).to eq(original_count + 1)
-
1
def and_call_original
-
and_wrap_original do |original, *args, &block|
-
original.call(*args, &block)
-
end
-
end
-
-
# Decorates the stubbed method with the supplied block. The original
-
# unmodified method is passed to the block along with any method call
-
# arguments so you can delegate to it, whilst still being able to
-
# change what args are passed to it and/or change the return value.
-
#
-
# @note This is only available on partial doubles.
-
#
-
# @example
-
#
-
# expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block|
-
# original_method.call(*args, &block).first(10)
-
# end
-
#
-
1
def and_wrap_original(&block)
-
if RSpec::Mocks::TestDouble === @method_double.object
-
@error_generator.raise_only_valid_on_a_partial_double(:and_call_original)
-
else
-
warn_about_stub_override if implementation.inner_action
-
@implementation = AndWrapOriginalImplementation.new(@method_double.original_method, block)
-
@yield_receiver_to_implementation_block = false
-
end
-
end
-
-
# @overload and_raise
-
# @overload and_raise(ExceptionClass)
-
# @overload and_raise(ExceptionClass, message)
-
# @overload and_raise(exception_instance)
-
#
-
# Tells the object to raise an exception when the message is received.
-
#
-
# @note
-
#
-
# When you pass an exception class, the MessageExpectation will raise
-
# an instance of it, creating it with `exception` and passing `message`
-
# if specified. If the exception class initializer requires more than
-
# one parameters, you must pass in an instance and not the class,
-
# otherwise this method will raise an ArgumentError exception.
-
#
-
# @example
-
#
-
# allow(car).to receive(:go).and_raise
-
# allow(car).to receive(:go).and_raise(OutOfGas)
-
# allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive")
-
# allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz))
-
1
def and_raise(exception=RuntimeError, message=nil)
-
4
if exception.respond_to?(:exception)
-
4
exception = message ? exception.exception(message) : exception.exception
-
end
-
-
8
self.terminal_implementation_action = Proc.new { raise exception }
-
nil
-
end
-
-
# @overload and_throw(symbol)
-
# @overload and_throw(symbol, object)
-
#
-
# Tells the object to throw a symbol (with the object if that form is
-
# used) when the message is received.
-
#
-
# @example
-
#
-
# allow(car).to receive(:go).and_throw(:out_of_gas)
-
# allow(car).to receive(:go).and_throw(:out_of_gas, :level => 0.1)
-
1
def and_throw(*args)
-
self.terminal_implementation_action = Proc.new { throw(*args) }
-
nil
-
end
-
-
# Tells the object to yield one or more args to a block when the message
-
# is received.
-
#
-
# @example
-
#
-
# stream.stub(:open).and_yield(StringIO.new)
-
1
def and_yield(*args, &block)
-
yield @eval_context = Object.new if block
-
@args_to_yield << args
-
self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator)
-
self
-
end
-
-
# @private
-
1
def matches?(message, *args)
-
4
@message == message && @argument_list_matcher.args_match?(*args)
-
end
-
-
# @private
-
1
def safe_invoke(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block)
-
end
-
-
# @private
-
1
def invoke(parent_stub, *args, &block)
-
4
invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
-
end
-
-
# @private
-
1
def invoke_without_incrementing_received_count(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block)
-
end
-
-
# @private
-
1
def negative?
-
4
@expected_received_count == 0 && !@at_least
-
end
-
-
# @private
-
1
def called_max_times?
-
@expected_received_count != :any &&
-
!@at_least &&
-
@expected_received_count > 0 &&
-
@actual_received_count >= @expected_received_count
-
end
-
-
# @private
-
1
def matches_name_but_not_args(message, *args)
-
@message == message && !@argument_list_matcher.args_match?(*args)
-
end
-
-
# @private
-
1
def verify_messages_received
-
InsertOntoBacktrace.line(@expected_from) do
-
generate_error unless expected_messages_received? || failed_fast?
-
end
-
end
-
-
# @private
-
1
def expected_messages_received?
-
ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count?
-
end
-
-
1
def ensure_expected_ordering_received!
-
@order_group.verify_invocation_order(self) if @ordered
-
true
-
end
-
-
# @private
-
1
def ignoring_args?
-
@expected_received_count == :any
-
end
-
-
# @private
-
1
def matches_at_least_count?
-
@at_least && @actual_received_count >= @expected_received_count
-
end
-
-
# @private
-
1
def matches_at_most_count?
-
@at_most && @actual_received_count <= @expected_received_count
-
end
-
-
# @private
-
1
def matches_exact_count?
-
@expected_received_count == @actual_received_count
-
end
-
-
# @private
-
1
def similar_messages
-
@similar_messages ||= []
-
end
-
-
# @private
-
1
def advise(*args)
-
similar_messages << args
-
end
-
-
# @private
-
1
def generate_error
-
if similar_messages.empty?
-
@error_generator.raise_expectation_error(@message, @expected_received_count, @argument_list_matcher, @actual_received_count, expectation_count_type, *expected_args)
-
else
-
@error_generator.raise_similar_message_args_error(self, *@similar_messages)
-
end
-
end
-
-
1
def expectation_count_type
-
return :at_least if @at_least
-
return :at_most if @at_most
-
nil
-
end
-
-
# @private
-
1
def description
-
@error_generator.describe_expectation(@message, @expected_received_count, @actual_received_count, *expected_args)
-
end
-
-
1
def raise_out_of_order_error
-
@error_generator.raise_out_of_order_error @message
-
end
-
-
# Constrains a stub or message expectation to invocations with specific
-
# arguments.
-
#
-
# With a stub, if the message might be received with other args as well,
-
# you should stub a default value first, and then stub or mock the same
-
# message using `with` to constrain to specific arguments.
-
#
-
# A message expectation will fail if the message is received with different
-
# arguments.
-
#
-
# @example
-
#
-
# allow(cart).to receive(:add) { :failure }
-
# allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
-
# cart.add(Book.new(:isbn => 1234567890))
-
# # => :failure
-
# cart.add(Book.new(:isbn => 1934356379))
-
# # => :success
-
#
-
# expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
-
# cart.add(Book.new(:isbn => 1234567890))
-
# # => failed expectation
-
# cart.add(Book.new(:isbn => 1934356379))
-
# # => passes
-
1
def with(*args, &block)
-
if args.empty?
-
raise ArgumentError,
-
"`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
-
end
-
-
self.inner_implementation_action = block
-
@argument_list_matcher = ArgumentListMatcher.new(*args)
-
self
-
end
-
-
# Constrain a message expectation to be received a specific number of
-
# times.
-
#
-
# @example
-
#
-
# expect(dealer).to receive(:deal_card).exactly(10).times
-
1
def exactly(n, &block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, n
-
self
-
end
-
-
# Constrain a message expectation to be received at least a specific
-
# number of times.
-
#
-
# @example
-
#
-
# expect(dealer).to receive(:deal_card).at_least(9).times
-
1
def at_least(n, &block)
-
set_expected_received_count :at_least, n
-
-
if n == 0
-
raise "at_least(0) has been removed, use allow(...).to receive(:message) instead"
-
end
-
-
self.inner_implementation_action = block
-
-
self
-
end
-
-
# Constrain a message expectation to be received at most a specific
-
# number of times.
-
#
-
# @example
-
#
-
# expect(dealer).to receive(:deal_card).at_most(10).times
-
1
def at_most(n, &block)
-
self.inner_implementation_action = block
-
set_expected_received_count :at_most, n
-
self
-
end
-
-
# Syntactic sugar for `exactly`, `at_least` and `at_most`
-
#
-
# @example
-
#
-
# expect(dealer).to receive(:deal_card).exactly(10).times
-
# expect(dealer).to receive(:deal_card).at_least(10).times
-
# expect(dealer).to receive(:deal_card).at_most(10).times
-
1
def times(&block)
-
self.inner_implementation_action = block
-
self
-
end
-
-
# Expect a message not to be received at all.
-
#
-
# @example
-
#
-
# expect(car).to receive(:stop).never
-
1
def never
-
ErrorGenerator.raise_double_negation_error("expect(obj)") if negative?
-
@expected_received_count = 0
-
self
-
end
-
-
# Expect a message to be received exactly one time.
-
#
-
# @example
-
#
-
# expect(car).to receive(:go).once
-
1
def once(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 1
-
self
-
end
-
-
# Expect a message to be received exactly two times.
-
#
-
# @example
-
#
-
# expect(car).to receive(:go).twice
-
1
def twice(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 2
-
self
-
end
-
-
# Expect a message to be received exactly three times.
-
#
-
# @example
-
#
-
# expect(car).to receive(:go).thrice
-
1
def thrice(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 3
-
self
-
end
-
-
# Expect messages to be received in a specific order.
-
#
-
# @example
-
#
-
# expect(api).to receive(:prepare).ordered
-
# expect(api).to receive(:run).ordered
-
# expect(api).to receive(:finish).ordered
-
1
def ordered(&block)
-
self.inner_implementation_action = block
-
additional_expected_calls.times do
-
@order_group.register(self)
-
end
-
@ordered = true
-
self
-
end
-
-
# @private
-
1
def additional_expected_calls
-
return 0 if @expectation_type == :stub || !@exactly
-
@expected_received_count - 1
-
end
-
-
# @private
-
1
def ordered?
-
4
@ordered
-
end
-
-
# @private
-
1
def negative_expectation_for?(message)
-
@message == message && negative?
-
end
-
-
# @private
-
1
def actual_received_count_matters?
-
@at_least || @at_most || @exactly
-
end
-
-
# @private
-
1
def increase_actual_received_count!
-
@actual_received_count += 1
-
end
-
-
1
private
-
-
1
def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
-
4
args.unshift(orig_object) if yield_receiver_to_implementation_block?
-
-
4
if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
-
@actual_received_count += increment
-
@failed_fast = true
-
# args are the args we actually received, @argument_list_matcher is the
-
# list of args we were expecting
-
@error_generator.raise_expectation_error(@message, @expected_received_count, @argument_list_matcher, @actual_received_count, expectation_count_type, *args)
-
end
-
-
4
@order_group.handle_order_constraint self
-
-
4
begin
-
4
if implementation.present?
-
4
implementation.call(*args, &block)
-
elsif parent_stub
-
parent_stub.invoke(nil, *args, &block)
-
end
-
ensure
-
4
@actual_received_count += increment
-
end
-
end
-
-
1
def failed_fast?
-
@failed_fast
-
end
-
-
1
def set_expected_received_count(relativity, n)
-
@at_least = (relativity == :at_least)
-
@at_most = (relativity == :at_most)
-
@exactly = (relativity == :exactly)
-
@expected_received_count = case n
-
when Numeric then n
-
when :once then 1
-
when :twice then 2
-
when :thrice then 3
-
end
-
end
-
-
1
def initial_implementation_action=(action)
-
implementation.initial_action = action
-
end
-
-
1
def inner_implementation_action=(action)
-
4
return unless action
-
warn_about_stub_override if implementation.inner_action
-
implementation.inner_action = action
-
end
-
-
1
def terminal_implementation_action=(action)
-
4
implementation.terminal_action = action
-
end
-
-
1
def warn_about_stub_override
-
RSpec.warning(
-
"You're overriding a previous stub implementation of `#{@message}`. " \
-
"Called from #{CallerFilter.first_non_rspec_line}."
-
)
-
end
-
end
-
-
# Handles the implementation of an `and_yield` declaration.
-
# @private
-
1
class AndYieldImplementation
-
1
def initialize(args_to_yield, eval_context, error_generator)
-
@args_to_yield = args_to_yield
-
@eval_context = eval_context
-
@error_generator = error_generator
-
end
-
-
1
def call(*_args_to_ignore, &block)
-
return if @args_to_yield.empty? && @eval_context.nil?
-
-
@error_generator.raise_missing_block_error @args_to_yield unless block
-
value = nil
-
block_signature = Support::BlockSignature.new(block)
-
-
@args_to_yield.each do |args|
-
unless Support::StrictSignatureVerifier.new(block_signature, args).valid?
-
@error_generator.raise_wrong_arity_error(args, block_signature)
-
end
-
-
value = @eval_context ? @eval_context.instance_exec(*args, &block) : block.call(*args)
-
end
-
value
-
end
-
end
-
-
# Handles the implementation of an `and_return` implementation.
-
# @private
-
1
class AndReturnImplementation
-
1
def initialize(values_to_return)
-
@values_to_return = values_to_return
-
end
-
-
1
def call(*_args_to_ignore, &_block)
-
if @values_to_return.size > 1
-
@values_to_return.shift
-
else
-
@values_to_return.first
-
end
-
end
-
end
-
-
# Represents a configured implementation. Takes into account
-
# any number of sub-implementations.
-
# @private
-
1
class Implementation
-
1
attr_accessor :initial_action, :inner_action, :terminal_action
-
-
1
def call(*args, &block)
-
actions.map do |action|
-
4
action.call(*args, &block)
-
4
end.last
-
end
-
-
1
def present?
-
4
actions.any?
-
end
-
-
1
private
-
-
1
def actions
-
8
[initial_action, inner_action, terminal_action].compact
-
end
-
end
-
-
# Represents an `and_call_original` implementation.
-
# @private
-
1
class AndWrapOriginalImplementation
-
1
def initialize(method, block)
-
@method = method
-
@block = block
-
end
-
-
1
CannotModifyFurtherError = Class.new(StandardError)
-
-
1
def initial_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def inner_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def terminal_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def present?
-
true
-
end
-
-
1
def inner_action
-
true
-
end
-
-
1
def call(*args, &block)
-
@block.call(@method, *args, &block)
-
end
-
-
1
private
-
-
1
def cannot_modify_further_error
-
CannotModifyFurtherError.new "This method has already been configured " \
-
"to call the original implementation, and cannot be modified further."
-
end
-
end
-
-
# Insert original locations into stacktraces
-
#
-
# @private
-
1
class InsertOntoBacktrace
-
1
def self.line(location)
-
yield
-
rescue RSpec::Mocks::MockExpectationError => error
-
error.backtrace.insert(0, location)
-
Kernel.raise error
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class MethodDouble
-
# @private
-
1
attr_reader :method_name, :object, :expectations, :stubs
-
-
# @private
-
1
def initialize(object, method_name, proxy)
-
4
@method_name = method_name
-
4
@object = object
-
4
@proxy = proxy
-
-
4
@original_visibility = nil
-
4
@method_stasher = InstanceMethodStasher.new(object, method_name)
-
4
@method_is_proxied = false
-
4
@expectations = []
-
4
@stubs = []
-
end
-
-
1
def original_method
-
# If original method is not present, uses the `method_missing`
-
# handler of the object. This accounts for cases where the user has not
-
# correctly defined `respond_to?`, and also 1.8 which does not provide
-
# method handles for missing methods even if `respond_to?` is correct.
-
@original_method ||=
-
@method_stasher.original_method ||
-
@proxy.original_method_handle_for(method_name) ||
-
Proc.new do |*args, &block|
-
@object.__send__(:method_missing, @method_name, *args, &block)
-
4
end
-
end
-
-
1
alias_method :save_original_method!, :original_method
-
-
# @private
-
1
def visibility
-
8
@proxy.visibility_for(@method_name)
-
end
-
-
# @private
-
1
def object_singleton_class
-
32
class << @object; self; end
-
end
-
-
# @private
-
1
def configure_method
-
4
@original_visibility = [visibility, method_name]
-
4
@method_stasher.stash unless @method_is_proxied
-
4
define_proxy_method
-
end
-
-
# @private
-
1
def define_proxy_method
-
4
return if @method_is_proxied
-
-
4
save_original_method!
-
4
definition_target.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
-
4
define_method(method_name) do |*args, &block|
-
4
method_double.proxy_method_invoked(self, *args, &block)
-
end
-
4
__send__(visibility, method_name)
-
end
-
-
4
@method_is_proxied = true
-
end
-
-
# The implementation of the proxied method. Subclasses may override this
-
# method to perform additional operations.
-
#
-
# @private
-
1
def proxy_method_invoked(_obj, *args, &block)
-
4
@proxy.message_received method_name, *args, &block
-
end
-
-
# @private
-
1
def restore_original_method
-
4
return show_frozen_warning if object_singleton_class.frozen?
-
4
return unless @method_is_proxied
-
-
4
definition_target.__send__(:remove_method, @method_name)
-
-
4
@method_stasher.restore if @method_stasher.method_is_stashed?
-
4
restore_original_visibility
-
-
4
@method_is_proxied = false
-
end
-
-
# @private
-
1
def show_frozen_warning
-
RSpec.warn_with(
-
"WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \
-
"method on #{@object.inspect} because it has been frozen. If you reuse this " \
-
"object, `#{@method_name}` will continue to respond with its stub implementation.",
-
:call_site => nil,
-
:use_spec_location_as_call_site => true
-
)
-
end
-
-
# @private
-
1
def restore_original_visibility
-
return unless @original_visibility &&
-
4
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
-
-
4
object_singleton_class.__send__(*@original_visibility)
-
end
-
-
# @private
-
1
def verify
-
4
expectations.each { |e| e.verify_messages_received }
-
end
-
-
# @private
-
1
def reset
-
4
restore_original_method
-
4
clear
-
end
-
-
# @private
-
1
def clear
-
4
expectations.clear
-
4
stubs.clear
-
end
-
-
# The type of message expectation to create has been extracted to its own
-
# method so that subclasses can override it.
-
#
-
# @private
-
1
def message_expectation_class
-
4
MessageExpectation
-
end
-
-
# @private
-
1
def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
-
configure_method
-
expectation = message_expectation_class.new(error_generator, expectation_ordering,
-
expected_from, self, :expectation, opts, &implementation)
-
expectations << expectation
-
expectation
-
end
-
-
# @private
-
1
def build_expectation(error_generator, expectation_ordering)
-
expected_from = IGNORED_BACKTRACE_LINE
-
message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
-
end
-
-
# @private
-
1
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
-
4
configure_method
-
4
stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
-
self, :stub, opts, &implementation)
-
4
stubs.unshift stub
-
4
stub
-
end
-
-
# A simple stub can only return a concrete value for a message, and
-
# cannot match on arguments. It is used as an optimization over
-
# `add_stub` / `add_expectation` where it is known in advance that this
-
# is all that will be required of a stub, such as when passing attributes
-
# to the `double` example method. They do not stash or restore existing method
-
# definitions.
-
#
-
# @private
-
1
def add_simple_stub(method_name, response)
-
setup_simple_method_double method_name, response, stubs
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, error_generator, backtrace_line)
-
setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
-
end
-
-
# @private
-
1
def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil)
-
define_proxy_method
-
-
me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
-
collection.unshift me
-
me
-
end
-
-
# @private
-
1
def add_default_stub(*args, &implementation)
-
return if stubs.any?
-
add_stub(*args, &implementation)
-
end
-
-
# @private
-
1
def remove_stub
-
raise_method_not_stubbed_error if stubs.empty?
-
remove_stub_if_present
-
end
-
-
# @private
-
1
def remove_stub_if_present
-
expectations.empty? ? reset : stubs.clear
-
end
-
-
# @private
-
1
def raise_method_not_stubbed_error
-
raise MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
-
end
-
-
# In Ruby 2.0.0 and above prepend will alter the method lookup chain.
-
# We use an object's singleton class to define method doubles upon,
-
# however if the object has had it's singleton class (as opposed to
-
# it's actual class) prepended too then the the method lookup chain
-
# will look in the prepended module first, **before** the singleton
-
# class.
-
#
-
# This code works around that by providing a mock definition target
-
# that is either the singleton class, or if necessary, a prepended module
-
# of our own.
-
#
-
1
if Support::RubyFeatures.module_prepends_supported?
-
-
1
private
-
-
# We subclass `Module` in order to be able to easily detect our prepended module.
-
1
RSpecPrependedModule = Class.new(Module)
-
-
1
def definition_target
-
8
@definition_target ||= usable_rspec_prepended_module || object_singleton_class
-
end
-
-
1
def usable_rspec_prepended_module
-
4
@proxy.prepended_modules_of_singleton_class.each do |mod|
-
# If we have one of our modules prepended before one of the user's
-
# modules that defines the method, use that, since our module's
-
# definition will take precedence.
-
return mod if RSpecPrependedModule === mod
-
-
# If we hit a user module with the method defined first,
-
# we must create a new prepend module, even if one exists later,
-
# because ours will only take precedence if it comes first.
-
return new_rspec_prepended_module if mod.method_defined?(method_name)
-
end
-
-
nil
-
end
-
-
1
def new_rspec_prepended_module
-
RSpecPrependedModule.new.tap do |mod|
-
object_singleton_class.__send__ :prepend, mod
-
end
-
end
-
-
else
-
-
private
-
-
def definition_target
-
object_singleton_class
-
end
-
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Represents a method on an object that may or may not be defined.
-
# The method may be an instance method on a module or a method on
-
# any object.
-
#
-
# @private
-
1
class MethodReference
-
1
def initialize(object_reference, method_name)
-
@object_reference = object_reference
-
@method_name = method_name
-
end
-
-
# A method is implemented if sending the message does not result in
-
# a `NoMethodError`. It might be dynamically implemented by
-
# `method_missing`.
-
1
def implemented?
-
@object_reference.when_loaded do |m|
-
method_implemented?(m)
-
end
-
end
-
-
# Returns true if we definitively know that sending the method
-
# will result in a `NoMethodError`.
-
#
-
# This is not simply the inverse of `implemented?`: there are
-
# cases when we don't know if a method is implemented and
-
# both `implemented?` and `unimplemented?` will return false.
-
1
def unimplemented?
-
@object_reference.when_loaded do |_m|
-
return !implemented?
-
end
-
-
# If it's not loaded, then it may be implemented but we can't check.
-
false
-
end
-
-
# A method is defined if we are able to get a `Method` object for it.
-
# In that case, we can assert against metadata like the arity.
-
1
def defined?
-
@object_reference.when_loaded do |m|
-
method_defined?(m)
-
end
-
end
-
-
1
def with_signature
-
return unless (original = original_method)
-
yield Support::MethodSignature.new(original)
-
end
-
-
1
def visibility
-
@object_reference.when_loaded do |m|
-
return visibility_from(m)
-
end
-
-
# When it's not loaded, assume it's public. We don't want to
-
# wrongly treat the method as private.
-
:public
-
end
-
-
1
private
-
-
1
def original_method
-
@object_reference.when_loaded do |m|
-
self.defined? && find_method(m)
-
end
-
end
-
-
1
def self.instance_method_visibility_for(klass, method_name)
-
20
if klass.public_method_defined?(method_name)
-
:public
-
20
elsif klass.private_method_defined?(method_name)
-
20
:private
-
elsif klass.protected_method_defined?(method_name)
-
:protected
-
end
-
end
-
-
1
class << self
-
1
alias method_defined_at_any_visibility? instance_method_visibility_for
-
end
-
-
1
def self.method_visibility_for(object, method_name)
-
16
instance_method_visibility_for(class << object; self; end, method_name).tap do |vis|
-
# If the method is not defined on the class, `instance_method_visibility_for`
-
# returns `nil`. However, it may be handled dynamically by `method_missing`,
-
# so here we check `respond_to` (passing false to not check private methods).
-
#
-
# This only considers the public case, but I don't think it's possible to
-
# write `method_missing` in such a way that it handles a dynamic message
-
# with private or protected visibility. Ruby doesn't provide you with
-
# the caller info.
-
8
return :public if vis.nil? && object.respond_to?(method_name, false)
-
end
-
end
-
end
-
-
# @private
-
1
class InstanceMethodReference < MethodReference
-
1
private
-
-
1
def method_implemented?(mod)
-
MethodReference.method_defined_at_any_visibility?(mod, @method_name)
-
end
-
-
# Ideally, we'd use `respond_to?` for `method_implemented?` but we need a
-
# reference to an instance to do that and we don't have one. Note that
-
# we may get false negatives: if the method is implemented via
-
# `method_missing`, we'll return `false` even though it meets our
-
# definition of "implemented". However, it's the best we can do.
-
1
alias method_defined? method_implemented?
-
-
# works around the fact that repeated calls for method parameters will
-
# falsely return empty arrays on JRuby in certain circumstances, this
-
# is necessary here because we can't dup/clone UnboundMethods.
-
#
-
# This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
-
# https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
-
1
if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
-
def find_method(mod)
-
mod.dup.instance_method(@method_name)
-
end
-
else
-
1
def find_method(mod)
-
mod.instance_method(@method_name)
-
end
-
end
-
-
1
def visibility_from(mod)
-
MethodReference.instance_method_visibility_for(mod, @method_name)
-
end
-
end
-
-
# @private
-
1
class ObjectMethodReference < MethodReference
-
1
private
-
-
1
def method_implemented?(object)
-
object.respond_to?(@method_name, true)
-
end
-
-
1
def method_defined?(object)
-
(class << object; self; end).method_defined?(@method_name)
-
end
-
-
1
def find_method(object)
-
object.method(@method_name)
-
end
-
-
1
def visibility_from(object)
-
MethodReference.method_visibility_for(object, @method_name)
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'recursive_const_methods'
-
-
1
module RSpec
-
1
module Mocks
-
# Provides information about constants that may (or may not)
-
# have been mutated by rspec-mocks.
-
1
class Constant
-
1
extend Support::RecursiveConstMethods
-
-
# @api private
-
1
def initialize(name)
-
@name = name
-
@previously_defined = false
-
@stubbed = false
-
@hidden = false
-
end
-
-
# @return [String] The fully qualified name of the constant.
-
1
attr_reader :name
-
-
# @return [Object, nil] The original value (e.g. before it
-
# was mutated by rspec-mocks) of the constant, or
-
# nil if the constant was not previously defined.
-
1
attr_accessor :original_value
-
-
# @private
-
1
attr_writer :previously_defined, :stubbed, :hidden
-
-
# @return [Boolean] Whether or not the constant was defined
-
# before the current example.
-
1
def previously_defined?
-
@previously_defined
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has mutated
-
# (stubbed or hidden) this constant.
-
1
def mutated?
-
@stubbed || @hidden
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has stubbed
-
# this constant.
-
1
def stubbed?
-
@stubbed
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has hidden
-
# this constant.
-
1
def hidden?
-
@hidden
-
end
-
-
# The default `to_s` isn't very useful, so a custom version is provided.
-
1
def to_s
-
"#<#{self.class.name} #{name}>"
-
end
-
1
alias inspect to_s
-
-
# @private
-
1
def self.unmutated(name)
-
const = new(name)
-
const.previously_defined = recursive_const_defined?(name)
-
const.stubbed = false
-
const.hidden = false
-
const.original_value = recursive_const_get(name) if const.previously_defined?
-
-
const
-
end
-
-
# Queries rspec-mocks to find out information about the named constant.
-
#
-
# @param [String] name the name of the constant
-
# @return [Constant] an object contaning information about the named
-
# constant.
-
1
def self.original(name)
-
mutator = ::RSpec::Mocks.space.constant_mutator_for(name)
-
mutator ? mutator.to_constant : unmutated(name)
-
end
-
end
-
-
# Provides a means to stub constants.
-
1
class ConstantMutator
-
1
extend Support::RecursiveConstMethods
-
-
# Stubs a constant.
-
#
-
# @param (see ExampleMethods#stub_const)
-
# @option (see ExampleMethods#stub_const)
-
# @return (see ExampleMethods#stub_const)
-
#
-
# @see ExampleMethods#stub_const
-
# @note It's recommended that you use `stub_const` in your
-
# examples. This is an alternate public API that is provided
-
# so you can stub constants in other contexts (e.g. helper
-
# classes).
-
1
def self.stub(constant_name, value, options={})
-
mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
-
DefinedConstantReplacer
-
else
-
UndefinedConstantSetter
-
end
-
-
mutate(mutator.new(constant_name, value, options[:transfer_nested_constants]))
-
value
-
end
-
-
# Hides a constant.
-
#
-
# @param (see ExampleMethods#hide_const)
-
#
-
# @see ExampleMethods#hide_const
-
# @note It's recommended that you use `hide_const` in your
-
# examples. This is an alternate public API that is provided
-
# so you can hide constants in other contexts (e.g. helper
-
# classes).
-
1
def self.hide(constant_name)
-
mutate(ConstantHider.new(constant_name, nil, {}))
-
nil
-
end
-
-
# Contains common functionality used by all of the constant mutators.
-
#
-
# @private
-
1
class BaseMutator
-
1
include Support::RecursiveConstMethods
-
-
1
attr_reader :original_value, :full_constant_name
-
-
1
def initialize(full_constant_name, mutated_value, transfer_nested_constants)
-
@full_constant_name = normalize_const_name(full_constant_name)
-
@mutated_value = mutated_value
-
@transfer_nested_constants = transfer_nested_constants
-
@context_parts = @full_constant_name.split('::')
-
@const_name = @context_parts.pop
-
@reset_performed = false
-
end
-
-
1
def to_constant
-
const = Constant.new(full_constant_name)
-
const.original_value = original_value
-
-
const
-
end
-
-
1
def idempotently_reset
-
reset unless @reset_performed
-
@reset_performed = true
-
end
-
end
-
-
# Hides a defined constant for the duration of an example.
-
#
-
# @private
-
1
class ConstantHider < BaseMutator
-
1
def mutate
-
return unless (@defined = recursive_const_defined?(full_constant_name))
-
@context = recursive_const_get(@context_parts.join('::'))
-
@original_value = get_const_defined_on(@context, @const_name)
-
-
@context.__send__(:remove_const, @const_name)
-
end
-
-
1
def to_constant
-
return Constant.unmutated(full_constant_name) unless @defined
-
-
const = super
-
const.hidden = true
-
const.previously_defined = true
-
-
const
-
end
-
-
1
def reset
-
return unless @defined
-
@context.const_set(@const_name, @original_value)
-
end
-
end
-
-
# Replaces a defined constant for the duration of an example.
-
#
-
# @private
-
1
class DefinedConstantReplacer < BaseMutator
-
1
def initialize(*args)
-
super
-
@constants_to_transfer = []
-
end
-
-
1
def mutate
-
@context = recursive_const_get(@context_parts.join('::'))
-
@original_value = get_const_defined_on(@context, @const_name)
-
-
@constants_to_transfer = verify_constants_to_transfer!
-
-
@context.__send__(:remove_const, @const_name)
-
@context.const_set(@const_name, @mutated_value)
-
-
transfer_nested_constants
-
end
-
-
1
def to_constant
-
const = super
-
const.stubbed = true
-
const.previously_defined = true
-
-
const
-
end
-
-
1
def reset
-
@constants_to_transfer.each do |const|
-
@mutated_value.__send__(:remove_const, const)
-
end
-
-
@context.__send__(:remove_const, @const_name)
-
@context.const_set(@const_name, @original_value)
-
end
-
-
1
def transfer_nested_constants
-
@constants_to_transfer.each do |const|
-
@mutated_value.const_set(const, get_const_defined_on(original_value, const))
-
end
-
end
-
-
1
def verify_constants_to_transfer!
-
return [] unless should_transfer_nested_constants?
-
-
{ @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
-
next if value.respond_to?(:constants)
-
-
raise ArgumentError,
-
"Cannot transfer nested constants for #{@full_constant_name} " \
-
"since #{description} is not a class or module and only classes " \
-
"and modules support nested constants."
-
end
-
-
if Array === @transfer_nested_constants
-
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
-
undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
-
-
if undefined_constants.any?
-
available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
-
raise ArgumentError,
-
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " \
-
"for #{@full_constant_name} since they are not defined. Did you mean " \
-
"#{available_constants.join(' or ')}?"
-
end
-
-
@transfer_nested_constants
-
else
-
constants_defined_on(@original_value)
-
end
-
end
-
-
1
def should_transfer_nested_constants?
-
return true if @transfer_nested_constants
-
return false unless RSpec::Mocks.configuration.transfer_nested_constants?
-
@original_value.respond_to?(:constants) && @mutated_value.respond_to?(:constants)
-
end
-
end
-
-
# Sets an undefined constant for the duration of an example.
-
#
-
# @private
-
1
class UndefinedConstantSetter < BaseMutator
-
1
def mutate
-
@parent = @context_parts.inject(Object) do |klass, name|
-
if const_defined_on?(klass, name)
-
get_const_defined_on(klass, name)
-
else
-
ConstantMutator.stub(name_for(klass, name), Module.new)
-
end
-
end
-
-
@parent.const_set(@const_name, @mutated_value)
-
end
-
-
1
def to_constant
-
const = super
-
const.stubbed = true
-
const.previously_defined = false
-
-
const
-
end
-
-
1
def reset
-
@parent.__send__(:remove_const, @const_name)
-
end
-
-
1
private
-
-
1
def name_for(parent, name)
-
root = if parent == Object
-
''
-
else
-
parent.name
-
end
-
root + '::' + name
-
end
-
end
-
-
# Uses the mutator to mutate (stub or hide) a constant. Ensures that
-
# the mutator is correctly registered so it can be backed out at the end
-
# of the test.
-
#
-
# @private
-
1
def self.mutate(mutator)
-
::RSpec::Mocks.space.register_constant_mutator(mutator)
-
mutator.mutate
-
end
-
-
# Used internally by the constant stubbing to raise a helpful
-
# error when a constant like "A::B::C" is stubbed and A::B is
-
# not a module (and thus, it's impossible to define "A::B::C"
-
# since only modules can have nested constants).
-
#
-
# @api private
-
1
def self.raise_on_invalid_const
-
lambda do |const_name, failed_name|
-
raise "Cannot stub constant #{failed_name} on #{const_name} " \
-
"since #{const_name} is not a module."
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class ObjectReference
-
# Returns an appropriate Object or Module reference based
-
# on the given argument.
-
1
def self.for(object_module_or_name, allow_direct_object_refs=false)
-
case object_module_or_name
-
when Module then DirectModuleReference.new(object_module_or_name)
-
when String then NamedObjectReference.new(object_module_or_name)
-
else
-
if allow_direct_object_refs
-
DirectObjectReference.new(object_module_or_name)
-
else
-
raise ArgumentError,
-
"Module or String expected, got #{object_module_or_name.inspect}"
-
end
-
end
-
end
-
end
-
-
# Used when an object is passed to `object_double`.
-
# Represents a reference to that object.
-
#
-
# @private
-
1
class DirectObjectReference
-
1
def initialize(object)
-
@object = object
-
end
-
-
1
def description
-
@object.inspect
-
end
-
-
1
def const_to_replace
-
raise ArgumentError,
-
"Can not perform constant replacement with an object."
-
end
-
-
1
def defined?
-
true
-
end
-
-
1
def when_loaded
-
yield @object
-
end
-
end
-
-
# Used when a module is passed to `class_double` or `instance_double`.
-
# Represents a reference to that module.
-
#
-
# @private
-
1
class DirectModuleReference < DirectObjectReference
-
1
def const_to_replace
-
@object.name
-
end
-
1
alias description const_to_replace
-
end
-
-
# Used when a string is passed to `class_double`, `instance_double`
-
# or `object_double`.
-
# Represents a reference to the object named (via a constant lookup)
-
# by the string.
-
#
-
# @private
-
1
class NamedObjectReference
-
1
def initialize(const_name)
-
@const_name = const_name
-
end
-
-
1
def defined?
-
!!object
-
end
-
-
1
def const_to_replace
-
@const_name
-
end
-
1
alias description const_to_replace
-
-
1
def when_loaded(&_block)
-
yield object if object
-
end
-
-
1
private
-
-
1
def object
-
@object ||= Constant.original(@const_name).original_value
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class OrderGroup
-
1
def initialize
-
63
@expectations = []
-
63
@invocation_order = []
-
63
@index = 0
-
end
-
-
# @private
-
1
def register(expectation)
-
@expectations << expectation
-
end
-
-
1
def invoked(message)
-
4
@invocation_order << message
-
end
-
-
# @private
-
1
def ready_for?(expectation)
-
remaining_expectations.find(&:ordered?) == expectation
-
end
-
-
# @private
-
1
def consume
-
remaining_expectations.each_with_index do |expectation, index|
-
next unless expectation.ordered?
-
-
@index += index + 1
-
return expectation
-
end
-
nil
-
end
-
-
# @private
-
1
def handle_order_constraint(expectation)
-
4
return unless expectation.ordered? && remaining_expectations.include?(expectation)
-
return consume if ready_for?(expectation)
-
expectation.raise_out_of_order_error
-
end
-
-
1
def verify_invocation_order(expectation)
-
expectation.raise_out_of_order_error unless expectations_invoked_in_order?
-
true
-
end
-
-
1
def clear
-
@index = 0
-
@invocation_order.clear
-
@expectations.clear
-
end
-
-
1
def empty?
-
@expectations.empty?
-
end
-
-
1
private
-
-
1
def remaining_expectations
-
@expectations[@index..-1] || []
-
end
-
-
1
def expectations_invoked_in_order?
-
invoked_expectations == expected_invocations
-
end
-
-
1
def invoked_expectations
-
@expectations.select { |e| e.ordered? && @invocation_order.include?(e) }
-
end
-
-
1
def expected_invocations
-
@invocation_order.map { |invocation| expectation_for(invocation) }.compact
-
end
-
-
1
def expectation_for(message)
-
@expectations.find { |e| message == e }
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class Proxy
-
1
SpecificMessage = Struct.new(:object, :message, :args) do
-
1
def ==(expectation)
-
expectation.orig_object == object && expectation.matches?(message, *args)
-
end
-
end
-
-
# @private
-
1
def ensure_implemented(*_args)
-
# noop for basic proxies, see VerifyingProxy for behaviour.
-
end
-
-
# @private
-
1
def initialize(object, order_group, name=nil, options={})
-
4
@object = object
-
4
@order_group = order_group
-
4
@name = name
-
4
@error_generator = ErrorGenerator.new(object, name)
-
4
@messages_received = []
-
4
@options = options
-
4
@null_object = false
-
8
@method_doubles = Hash.new { |h, k| h[k] = MethodDouble.new(@object, k, self) }
-
end
-
-
# @private
-
1
attr_reader :object
-
-
# @private
-
1
def null_object?
-
@null_object
-
end
-
-
# @private
-
# Tells the object to ignore any messages that aren't explicitly set as
-
# stubs or message expectations.
-
1
def as_null_object
-
@null_object = true
-
@object
-
end
-
-
# @private
-
1
def original_method_handle_for(_message)
-
nil
-
end
-
-
# @private
-
1
def add_message_expectation(method_name, opts={}, &block)
-
location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line }
-
meth_double = method_double_for(method_name)
-
-
if null_object? && !block
-
meth_double.add_default_stub(@error_generator, @order_group, location, opts) do
-
@object
-
end
-
end
-
-
meth_double.add_expectation @error_generator, @order_group, location, opts, &block
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, location)
-
method_double_for(method_name).add_simple_expectation method_name, response, @error_generator, location
-
end
-
-
# @private
-
1
def build_expectation(method_name)
-
meth_double = method_double_for(method_name)
-
-
meth_double.build_expectation(
-
@error_generator,
-
@order_group
-
)
-
end
-
-
# @private
-
1
def replay_received_message_on(expectation, &block)
-
expected_method_name = expectation.message
-
meth_double = method_double_for(expected_method_name)
-
-
if meth_double.expectations.any?
-
@error_generator.raise_expectation_on_mocked_method(expected_method_name)
-
end
-
-
unless null_object? || meth_double.stubs.any?
-
@error_generator.raise_expectation_on_unstubbed_method(expected_method_name)
-
end
-
-
@messages_received.each do |(actual_method_name, args, _)|
-
next unless expectation.matches?(actual_method_name, *args)
-
-
expectation.safe_invoke(nil)
-
block.call(*args) if block
-
end
-
end
-
-
# @private
-
1
def check_for_unexpected_arguments(expectation)
-
@messages_received.each do |(method_name, args, _)|
-
next unless expectation.matches_name_but_not_args(method_name, *args)
-
-
raise_unexpected_message_args_error(expectation, *args)
-
end
-
end
-
-
# @private
-
1
def add_stub(method_name, opts={}, &implementation)
-
8
location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line }
-
4
method_double_for(method_name).add_stub @error_generator, @order_group, location, opts, &implementation
-
end
-
-
# @private
-
1
def add_simple_stub(method_name, response)
-
method_double_for(method_name).add_simple_stub method_name, response
-
end
-
-
# @private
-
1
def remove_stub(method_name)
-
method_double_for(method_name).remove_stub
-
end
-
-
# @private
-
1
def remove_stub_if_present(method_name)
-
method_double_for(method_name).remove_stub_if_present
-
end
-
-
# @private
-
1
def verify
-
8
@method_doubles.each_value { |d| d.verify }
-
end
-
-
# @private
-
1
def reset
-
4
@messages_received.clear
-
end
-
-
# @private
-
1
def received_message?(method_name, *args, &block)
-
@messages_received.any? { |array| array == [method_name, args, block] }
-
end
-
-
# @private
-
1
def has_negative_expectation?(message)
-
method_double_for(message).expectations.find { |expectation| expectation.negative_expectation_for?(message) }
-
end
-
-
# @private
-
1
def record_message_received(message, *args, &block)
-
4
@order_group.invoked SpecificMessage.new(object, message, args)
-
4
@messages_received << [message, args, block]
-
end
-
-
# @private
-
1
def message_received(message, *args, &block)
-
4
record_message_received message, *args, &block
-
-
4
expectation = find_matching_expectation(message, *args)
-
4
stub = find_matching_method_stub(message, *args)
-
-
4
if (stub && expectation && expectation.called_max_times?) || (stub && !expectation)
-
4
expectation.increase_actual_received_count! if expectation && expectation.actual_received_count_matters?
-
4
if (expectation = find_almost_matching_expectation(message, *args))
-
expectation.advise(*args) unless expectation.expected_messages_received?
-
end
-
4
stub.invoke(nil, *args, &block)
-
elsif expectation
-
expectation.invoke(stub, *args, &block)
-
elsif (expectation = find_almost_matching_expectation(message, *args))
-
expectation.advise(*args) if null_object? unless expectation.expected_messages_received?
-
-
if null_object? || !has_negative_expectation?(message)
-
raise_unexpected_message_args_error(expectation, *args)
-
end
-
elsif (stub = find_almost_matching_stub(message, *args))
-
stub.advise(*args)
-
raise_missing_default_stub_error(stub, *args)
-
elsif Class === @object
-
@object.superclass.__send__(message, *args, &block)
-
else
-
@object.__send__(:method_missing, message, *args, &block)
-
end
-
end
-
-
# @private
-
1
def raise_unexpected_message_error(method_name, *args)
-
@error_generator.raise_unexpected_message_error method_name, *args
-
end
-
-
# @private
-
1
def raise_unexpected_message_args_error(expectation, *args)
-
@error_generator.raise_unexpected_message_args_error(expectation, *args)
-
end
-
-
# @private
-
1
def raise_missing_default_stub_error(expectation, *args)
-
@error_generator.raise_missing_default_stub_error(expectation, *args)
-
end
-
-
# @private
-
1
def visibility_for(_method_name)
-
# This is the default (for test doubles). Subclasses override this.
-
:public
-
end
-
-
1
if Support::RubyFeatures.module_prepends_supported?
-
1
def self.prepended_modules_of(klass)
-
4
ancestors = klass.ancestors
-
-
# `|| 0` is necessary for Ruby 2.0, where the singleton class
-
# is only in the ancestor list when there are prepended modules.
-
4
singleton_index = ancestors.index(klass) || 0
-
-
4
ancestors[0, singleton_index]
-
end
-
-
1
def prepended_modules_of_singleton_class
-
4
@prepended_modules_of_singleton_class ||= RSpec::Mocks::Proxy.prepended_modules_of(@object.singleton_class)
-
end
-
end
-
-
1
private
-
-
1
def method_double_for(message)
-
16
@method_doubles[message.to_sym]
-
end
-
-
1
def find_matching_expectation(method_name, *args)
-
4
find_best_matching_expectation_for(method_name) do |expectation|
-
expectation.matches?(method_name, *args)
-
end
-
end
-
-
1
def find_almost_matching_expectation(method_name, *args)
-
4
find_best_matching_expectation_for(method_name) do |expectation|
-
expectation.matches_name_but_not_args(method_name, *args)
-
end
-
end
-
-
1
def find_best_matching_expectation_for(method_name)
-
8
first_match = nil
-
-
8
method_double_for(method_name).expectations.each do |expectation|
-
next unless yield expectation
-
return expectation unless expectation.called_max_times?
-
first_match ||= expectation
-
end
-
-
8
first_match
-
end
-
-
1
def find_matching_method_stub(method_name, *args)
-
8
method_double_for(method_name).stubs.find { |stub| stub.matches?(method_name, *args) }
-
end
-
-
1
def find_almost_matching_stub(method_name, *args)
-
method_double_for(method_name).stubs.find { |stub| stub.matches_name_but_not_args(method_name, *args) }
-
end
-
end
-
-
# @private
-
1
class TestDoubleProxy < Proxy
-
1
def reset
-
@method_doubles.clear
-
object.__disallow_further_usage!
-
super
-
end
-
end
-
-
# @private
-
1
class PartialDoubleProxy < Proxy
-
1
def original_method_handle_for(message)
-
4
if any_instance_class_recorder_observing_method?(@object.class, message)
-
message = ::RSpec::Mocks.space.
-
any_instance_recorder_for(@object.class).
-
build_alias_method_name(message)
-
end
-
-
4
::RSpec::Support.method_handle_for(@object, message)
-
rescue NameError
-
nil
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, location)
-
method_double_for(method_name).configure_method
-
super
-
end
-
-
# @private
-
1
def add_simple_stub(method_name, response)
-
method_double_for(method_name).configure_method
-
super
-
end
-
-
# @private
-
1
def visibility_for(method_name)
-
# We fall back to :public because by default we allow undefined methods
-
# to be stubbed, and when we do so, we make them public.
-
8
MethodReference.method_visibility_for(@object, method_name) || :public
-
end
-
-
1
def reset
-
8
@method_doubles.each_value { |d| d.reset }
-
4
super
-
end
-
-
1
def message_received(message, *args, &block)
-
4
RSpec::Mocks.space.any_instance_recorders_from_ancestry_of(object).each do |subscriber|
-
subscriber.notify_received_message(object, message, args, block)
-
end
-
4
super
-
end
-
-
1
private
-
-
1
def any_instance_class_recorder_observing_method?(klass, method_name)
-
16
only_return_existing = true
-
16
recorder = ::RSpec::Mocks.space.any_instance_recorder_for(klass, only_return_existing)
-
16
return true if recorder && recorder.already_observing?(method_name)
-
-
16
superklass = klass.superclass
-
16
return false if superklass.nil?
-
12
any_instance_class_recorder_observing_method?(superklass, method_name)
-
end
-
end
-
-
# @private
-
# When we mock or stub a method on a class, we have to treat it a bit different,
-
# because normally singleton method definitions only affect the object on which
-
# they are defined, but on classes they affect subclasses, too. As a result,
-
# we need some special handling to get the original method.
-
1
module PartialClassDoubleProxyMethods
-
1
def initialize(source_space, *args)
-
@source_space = source_space
-
super(*args)
-
end
-
-
# Consider this situation:
-
#
-
# class A; end
-
# class B < A; end
-
#
-
# allow(A).to receive(:new)
-
# expect(B).to receive(:new).and_call_original
-
#
-
# When getting the original definition for `B.new`, we cannot rely purely on
-
# using `B.method(:new)` before our redefinition is defined on `B`, because
-
# `B.method(:new)` will return a method that will execute the stubbed version
-
# of the method on `A` since singleton methods on classes are in the lookup
-
# hierarchy.
-
#
-
# To do it properly, we need to find the original definition of `new` from `A`
-
# from _before_ `A` was stubbed, and we need to rebind it to `B` so that it will
-
# run with the proper `self`.
-
#
-
# That's what this method (together with `original_unbound_method_handle_from_ancestor_for`)
-
# does.
-
1
def original_method_handle_for(message)
-
unbound_method = superclass_proxy &&
-
superclass_proxy.original_unbound_method_handle_from_ancestor_for(message.to_sym)
-
-
return super unless unbound_method
-
unbound_method.bind(object)
-
end
-
-
1
protected
-
-
1
def original_unbound_method_handle_from_ancestor_for(message)
-
method_double = @method_doubles.fetch(message) do
-
# The fact that there is no method double for this message indicates
-
# that it has not been redefined by rspec-mocks. We need to continue
-
# looking up the ancestor chain.
-
return superclass_proxy &&
-
superclass_proxy.original_unbound_method_handle_from_ancestor_for(message)
-
end
-
-
method_double.original_method.unbind
-
end
-
-
1
def superclass_proxy
-
return @superclass_proxy if defined?(@superclass_proxy)
-
-
if (superclass = object.superclass)
-
@superclass_proxy = @source_space.proxy_for(superclass)
-
else
-
@superclass_proxy = nil
-
end
-
end
-
end
-
-
# @private
-
1
class PartialClassDoubleProxy < PartialDoubleProxy
-
1
include PartialClassDoubleProxyMethods
-
end
-
-
# @private
-
1
class ProxyForNil < PartialDoubleProxy
-
1
def initialize(order_group)
-
@warn_about_expectations = true
-
super(nil, order_group)
-
end
-
-
1
attr_accessor :warn_about_expectations
-
1
alias warn_about_expectations? warn_about_expectations
-
-
1
def add_message_expectation(method_name, opts={}, &block)
-
warn(method_name) if warn_about_expectations?
-
super
-
end
-
-
1
def add_negative_message_expectation(location, method_name, &implementation)
-
warn(method_name) if warn_about_expectations?
-
super
-
end
-
-
1
def add_stub(method_name, opts={}, &implementation)
-
warn(method_name) if warn_about_expectations?
-
super
-
end
-
-
1
private
-
-
1
def warn(method_name)
-
source = CallerFilter.first_non_rspec_line
-
Kernel.warn("An expectation of :#{method_name} was set on nil. Called from #{source}. Use allow_message_expectations_on_nil to disable warnings.")
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
# Provides a default space implementation for outside
-
# the scope of an example. Called "root" because it serves
-
# as the root of the space stack.
-
1
class RootSpace
-
1
def proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_recorder_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def register_constant_mutator(_mutator)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_recorders_from_ancestry_of(_object)
-
raise_lifecycle_message
-
end
-
-
1
def reset_all
-
end
-
-
1
def verify_all
-
end
-
-
1
def registered?(_object)
-
false
-
end
-
-
1
def new_scope
-
63
Space.new
-
end
-
-
1
private
-
-
1
def raise_lifecycle_message
-
raise OutsideOfExampleError,
-
"The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported."
-
end
-
end
-
-
# @private
-
1
class Space
-
1
attr_reader :proxies, :any_instance_recorders, :proxy_mutex, :any_instance_mutex
-
-
1
def initialize
-
63
@proxies = {}
-
63
@any_instance_recorders = {}
-
63
@constant_mutators = []
-
63
@expectation_ordering = OrderGroup.new
-
63
@proxy_mutex = new_mutex
-
63
@any_instance_mutex = new_mutex
-
end
-
-
1
def new_scope
-
NestedSpace.new(self)
-
end
-
-
1
def verify_all
-
67
proxies.values.each { |proxy| proxy.verify }
-
63
any_instance_recorders.each_value { |recorder| recorder.verify }
-
end
-
-
1
def reset_all
-
67
proxies.each_value { |proxy| proxy.reset }
-
63
@constant_mutators.reverse.each { |mut| mut.idempotently_reset }
-
63
any_instance_recorders.each_value { |recorder| recorder.stop_all_observation! }
-
63
any_instance_recorders.clear
-
end
-
-
1
def register_constant_mutator(mutator)
-
@constant_mutators << mutator
-
end
-
-
1
def constant_mutator_for(name)
-
@constant_mutators.find { |m| m.full_constant_name == name }
-
end
-
-
1
def any_instance_recorder_for(klass, only_return_existing=false)
-
16
any_instance_mutex.synchronize do
-
16
id = klass.__id__
-
16
any_instance_recorders.fetch(id) do
-
16
return nil if only_return_existing
-
any_instance_recorder_not_found_for(id, klass)
-
end
-
end
-
end
-
-
1
def any_instance_proxy_for(klass)
-
AnyInstance::Proxy.new(any_instance_recorder_for(klass), proxies_of(klass))
-
end
-
-
1
def proxies_of(klass)
-
proxies.values.select { |proxy| klass === proxy.object }
-
end
-
-
1
def proxy_for(object)
-
4
proxy_mutex.synchronize do
-
4
id = id_for(object)
-
8
proxies.fetch(id) { proxy_not_found_for(id, object) }
-
end
-
end
-
-
1
alias ensure_registered proxy_for
-
-
1
def registered?(object)
-
proxies.key?(id_for object)
-
end
-
-
1
def any_instance_recorders_from_ancestry_of(object)
-
# Optimization: `any_instance` is a feature we generally
-
# recommend not using, so we can often early exit here
-
# without doing an O(N) linear search over the number of
-
# ancestors in the object's class hierarchy.
-
4
return [] if any_instance_recorders.empty?
-
-
# We access the ancestors through the singleton class, to avoid calling
-
# `class` in case `class` has been stubbed.
-
(class << object; ancestors; end).map do |klass|
-
any_instance_recorders[klass.__id__]
-
end.compact
-
end
-
-
1
private
-
-
# We don't want to depend on the stdlib ourselves, but if the user is
-
# using threads then a Mutex will be available to us. If not, we don't
-
# need to synchronize anyway.
-
1
def new_mutex
-
126
defined?(::Mutex) ? ::Mutex.new : FakeMutex
-
end
-
-
# @private
-
1
module FakeMutex
-
1
def self.synchronize
-
yield
-
end
-
end
-
-
1
def proxy_not_found_for(id, object)
-
4
proxies[id] = case object
-
when NilClass then ProxyForNil.new(@expectation_ordering)
-
when TestDouble then object.__build_mock_proxy_unless_expired(@expectation_ordering)
-
when Class
-
if RSpec::Mocks.configuration.verify_partial_doubles?
-
VerifyingPartialClassDoubleProxy.new(self, object, @expectation_ordering)
-
else
-
PartialClassDoubleProxy.new(self, object, @expectation_ordering)
-
end
-
else
-
4
if RSpec::Mocks.configuration.verify_partial_doubles?
-
VerifyingPartialDoubleProxy.new(object, @expectation_ordering)
-
else
-
4
PartialDoubleProxy.new(object, @expectation_ordering)
-
end
-
end
-
end
-
-
1
def any_instance_recorder_not_found_for(id, klass)
-
any_instance_recorders[id] = AnyInstance::Recorder.new(klass)
-
end
-
-
1
if defined?(::BasicObject) && !::BasicObject.method_defined?(:__id__) # for 1.9.2
-
require 'securerandom'
-
-
def id_for(object)
-
id = object.__id__
-
-
return id if object.equal?(::ObjectSpace._id2ref(id))
-
# this suggests that object.__id__ is proxying through to some wrapped object
-
-
object.instance_exec do
-
@__id_for_rspec_mocks_space ||= ::SecureRandom.uuid
-
end
-
end
-
else
-
1
def id_for(object)
-
4
object.__id__
-
end
-
end
-
end
-
-
# @private
-
1
class NestedSpace < Space
-
1
def initialize(parent)
-
@parent = parent
-
super()
-
end
-
-
1
def proxies_of(klass)
-
super + @parent.proxies_of(klass)
-
end
-
-
1
def constant_mutator_for(name)
-
super || @parent.constant_mutator_for(name)
-
end
-
-
1
def registered?(object)
-
super || @parent.registered?(object)
-
end
-
-
1
private
-
-
1
def proxy_not_found_for(id, object)
-
@parent.proxies[id] || super
-
end
-
-
1
def any_instance_recorder_not_found_for(id, klass)
-
@parent.any_instance_recorders[id] || super
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @api private
-
# Provides methods for enabling and disabling the available syntaxes
-
# provided by rspec-mocks.
-
1
module Syntax
-
# @private
-
1
def self.warn_about_should!
-
1
@warn_about_should = true
-
end
-
-
# @private
-
1
def self.warn_unless_should_configured(method_name , replacement="the new `:expect` syntax or explicitly enable `:should`")
-
if @warn_about_should
-
RSpec.deprecate(
-
"Using `#{method_name}` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax",
-
:replacement => replacement
-
)
-
-
@warn_about_should = false
-
end
-
end
-
-
# @api private
-
# Enables the should syntax (`dbl.stub`, `dbl.should_receive`, etc).
-
1
def self.enable_should(syntax_host=default_should_syntax_host)
-
1
@warn_about_should = false if syntax_host == default_should_syntax_host
-
1
return if should_enabled?(syntax_host)
-
-
1
syntax_host.class_exec do
-
1
def should_receive(message, opts={}, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.expect_message(self, message, opts, &block)
-
end
-
-
1
def should_not_receive(message, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.expect_message(self, message, {}, &block).never
-
end
-
-
1
def stub(message_or_hash, opts={}, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
if ::Hash === message_or_hash
-
message_or_hash.each { |message, value| stub(message).and_return value }
-
else
-
::RSpec::Mocks.allow_message(self, message_or_hash, opts, &block)
-
end
-
end
-
-
1
def unstub(message)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__, "`allow(...).to_receive(...).and_call_original` or explicitly enable `:should`")
-
::RSpec::Mocks.space.proxy_for(self).remove_stub(message)
-
end
-
-
1
def stub_chain(*chain, &blk)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks::StubChain.stub_chain_on(self, *chain, &blk)
-
end
-
-
1
def as_null_object
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
@_null_object = true
-
::RSpec::Mocks.space.proxy_for(self).as_null_object
-
end
-
-
1
def null_object?
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
defined?(@_null_object)
-
end
-
-
1
def received_message?(message, *args, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.space.proxy_for(self).received_message?(message, *args, &block)
-
end
-
-
1
unless Class.respond_to? :any_instance
-
1
Class.class_exec do
-
1
def any_instance
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.space.any_instance_proxy_for(self)
-
end
-
end
-
end
-
end
-
end
-
-
# @api private
-
# Disables the should syntax (`dbl.stub`, `dbl.should_receive`, etc).
-
1
def self.disable_should(syntax_host=default_should_syntax_host)
-
return unless should_enabled?(syntax_host)
-
-
syntax_host.class_exec do
-
undef should_receive
-
undef should_not_receive
-
undef stub
-
undef unstub
-
undef stub_chain
-
undef as_null_object
-
undef null_object?
-
undef received_message?
-
end
-
-
Class.class_exec do
-
undef any_instance
-
end
-
end
-
-
# @api private
-
# Enables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
-
1
def self.enable_expect(syntax_host=::RSpec::Mocks::ExampleMethods)
-
1
return if expect_enabled?(syntax_host)
-
-
1
syntax_host.class_exec do
-
1
def receive(method_name, &block)
-
4
Matchers::Receive.new(method_name, block)
-
end
-
-
1
def receive_messages(message_return_value_hash)
-
matcher = Matchers::ReceiveMessages.new(message_return_value_hash)
-
matcher.warn_about_block if block_given?
-
matcher
-
end
-
-
1
def receive_message_chain(*messages, &block)
-
Matchers::ReceiveMessageChain.new(messages, &block)
-
end
-
-
1
def allow(target)
-
4
AllowanceTarget.new(target)
-
end
-
-
1
def expect_any_instance_of(klass)
-
AnyInstanceExpectationTarget.new(klass)
-
end
-
-
1
def allow_any_instance_of(klass)
-
AnyInstanceAllowanceTarget.new(klass)
-
end
-
end
-
-
1
RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do
-
1
def expect(target)
-
ExpectationTarget.new(target)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
-
1
def self.disable_expect(syntax_host=::RSpec::Mocks::ExampleMethods)
-
return unless expect_enabled?(syntax_host)
-
-
syntax_host.class_exec do
-
undef receive
-
undef receive_messages
-
undef receive_message_chain
-
undef allow
-
undef expect_any_instance_of
-
undef allow_any_instance_of
-
end
-
-
RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do
-
undef expect
-
end
-
end
-
-
# @api private
-
# Indicates whether or not the should syntax is enabled.
-
1
def self.should_enabled?(syntax_host=default_should_syntax_host)
-
1
syntax_host.method_defined?(:should_receive)
-
end
-
-
# @api private
-
# Indicates whether or not the expect syntax is enabled.
-
1
def self.expect_enabled?(syntax_host=::RSpec::Mocks::ExampleMethods)
-
1
syntax_host.method_defined?(:allow)
-
end
-
-
# @api private
-
# Determines where the methods like `should_receive`, and `stub` are added.
-
1
def self.default_should_syntax_host
-
# JRuby 1.7.4 introduces a regression whereby `defined?(::BasicObject) => nil`
-
# yet `BasicObject` still exists and patching onto ::Object breaks things
-
# e.g. SimpleDelegator expectations won't work
-
#
-
# See: https://github.com/jruby/jruby/issues/814
-
2
if defined?(JRUBY_VERSION) && JRUBY_VERSION == '1.7.4' && RUBY_VERSION.to_f > 1.8
-
return ::BasicObject
-
end
-
-
# On 1.8.7, Object.ancestors.last == Kernel but
-
# things blow up if we include `RSpec::Mocks::Methods`
-
# into Kernel...not sure why.
-
2
return Object unless defined?(::BasicObject)
-
-
# MacRuby has BasicObject but it's not the root class.
-
2
return Object unless Object.ancestors.last == ::BasicObject
-
-
2
::BasicObject
-
end
-
end
-
end
-
end
-
-
1
if defined?(BasicObject)
-
# The legacy `:should` syntax adds the following methods directly to
-
# `BasicObject` so that they are available off of any object. Note, however,
-
# that this syntax does not always play nice with delegate/proxy objects.
-
# We recommend you use the non-monkeypatching `:expect` syntax instead.
-
# @see Class
-
1
class BasicObject
-
# @method should_receive
-
# Sets an expectation that this object should receive a message before
-
# the end of the example.
-
#
-
# @example
-
#
-
# logger = double('logger')
-
# thing_that_logs = ThingThatLogs.new(logger)
-
# logger.should_receive(:log)
-
# thing_that_logs.do_something_that_logs_a_message
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#expect
-
-
# @method should_not_receive
-
# Sets and expectation that this object should _not_ receive a message
-
# during this example.
-
# @see RSpec::Mocks::ExampleMethods#expect
-
-
# @method stub
-
# Tells the object to respond to the message with the specified value.
-
#
-
# @example
-
#
-
# counter.stub(:count).and_return(37)
-
# counter.stub(:count => 37)
-
# counter.stub(:count) { 37 }
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#allow
-
-
# @method unstub
-
# Removes a stub. On a double, the object will no longer respond to
-
# `message`. On a real object, the original method (if it exists) is
-
# restored.
-
#
-
# This is rarely used, but can be useful when a stub is set up during a
-
# shared `before` hook for the common case, but you want to replace it
-
# for a special case.
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
-
# @method stub_chain
-
# @overload stub_chain(method1, method2)
-
# @overload stub_chain("method1.method2")
-
# @overload stub_chain(method1, method_to_value_hash)
-
#
-
# Stubs a chain of methods.
-
#
-
# ## Warning:
-
#
-
# Chains can be arbitrarily long, which makes it quite painless to
-
# violate the Law of Demeter in violent ways, so you should consider any
-
# use of `stub_chain` a code smell. Even though not all code smells
-
# indicate real problems (think fluent interfaces), `stub_chain` still
-
# results in brittle examples. For example, if you write
-
# `foo.stub_chain(:bar, :baz => 37)` in a spec and then the
-
# implementation calls `foo.baz.bar`, the stub will not work.
-
#
-
# @example
-
#
-
# double.stub_chain("foo.bar") { :baz }
-
# double.stub_chain(:foo, :bar => :baz)
-
# double.stub_chain(:foo, :bar) { :baz }
-
#
-
# # Given any of ^^ these three forms ^^:
-
# double.foo.bar # => :baz
-
#
-
# # Common use in Rails/ActiveRecord:
-
# Article.stub_chain("recent.published") { [Article.new] }
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#receive_message_chain
-
-
# @method as_null_object
-
# Tells the object to respond to all messages. If specific stub values
-
# are declared, they'll work as expected. If not, the receiver is
-
# returned.
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
-
# @method null_object?
-
# Returns true if this object has received `as_null_object`
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
end
-
end
-
-
# The legacy `:should` syntax adds the `any_instance` to `Class`.
-
# We generally recommend you use the newer `:expect` syntax instead,
-
# which allows you to stub any instance of a class using
-
# `allow_any_instance_of(klass)` or mock any instance using
-
# `expect_any_instance_of(klass)`.
-
# @see BasicObject
-
1
class Class
-
# @method any_instance
-
# Used to set stubs and message expectations on any instance of a given
-
# class. Returns a [Recorder](Recorder), which records messages like
-
# `stub` and `should_receive` for later playback on instances of the
-
# class.
-
#
-
# @example
-
#
-
# Car.any_instance.should_receive(:go)
-
# race = Race.new
-
# race.cars << Car.new
-
# race.go # assuming this delegates to all of its cars
-
# # this example would pass
-
#
-
# Account.any_instance.stub(:balance) { Money.new(:USD, 25) }
-
# Account.new.balance # => Money.new(:USD, 25))
-
#
-
# @return [Recorder]
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#expect_any_instance_of
-
# @see RSpec::Mocks::ExampleMethods#allow_any_instance_of
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class TargetBase
-
1
def initialize(target)
-
4
@target = target
-
end
-
-
1
def self.delegate_to(matcher_method)
-
4
define_method(:to) do |matcher, &block|
-
4
unless matcher_allowed?(matcher)
-
raise_unsupported_matcher(:to, matcher)
-
end
-
4
define_matcher(matcher, matcher_method, &block)
-
end
-
end
-
-
1
def self.delegate_not_to(matcher_method, options={})
-
4
method_name = options.fetch(:from)
-
4
define_method(method_name) do |matcher, &block|
-
case matcher
-
when Matchers::Receive
-
define_matcher(matcher, matcher_method, &block)
-
when Matchers::ReceiveMessages, Matchers::ReceiveMessageChain
-
raise_negation_unsupported(method_name, matcher)
-
else
-
raise_unsupported_matcher(method_name, matcher)
-
end
-
end
-
end
-
-
1
def self.disallow_negation(method_name)
-
4
define_method(method_name) do |matcher, *_args|
-
raise_negation_unsupported(method_name, matcher)
-
end
-
end
-
-
1
private
-
-
1
def matcher_allowed?(matcher)
-
4
matcher.class.name.start_with?("RSpec::Mocks::Matchers".freeze)
-
end
-
-
1
def define_matcher(matcher, name, &block)
-
4
matcher.__send__(name, @target, &block)
-
end
-
-
1
def raise_unsupported_matcher(method_name, matcher)
-
raise UnsupportedMatcherError,
-
"only the `receive` or `receive_messages` matchers are supported " \
-
"with `#{expression}(...).#{method_name}`, but you have provided: #{matcher}"
-
end
-
-
1
def raise_negation_unsupported(method_name, matcher)
-
raise NegationUnsupportedError,
-
"`#{expression}(...).#{method_name} #{matcher.name}` is not supported since it " \
-
"doesn't really make sense. What would it even mean?"
-
end
-
-
1
def expression
-
self.class::EXPRESSION
-
end
-
end
-
-
# @private
-
1
class AllowanceTarget < TargetBase
-
1
EXPRESSION = :allow
-
1
delegate_to :setup_allowance
-
1
disallow_negation :not_to
-
1
disallow_negation :to_not
-
end
-
-
# @private
-
1
class ExpectationTarget < TargetBase
-
1
EXPRESSION = :expect
-
1
delegate_to :setup_expectation
-
1
delegate_not_to :setup_negative_expectation, :from => :not_to
-
1
delegate_not_to :setup_negative_expectation, :from => :to_not
-
end
-
-
# @private
-
1
class AnyInstanceAllowanceTarget < TargetBase
-
1
EXPRESSION = :allow_any_instance_of
-
1
delegate_to :setup_any_instance_allowance
-
1
disallow_negation :not_to
-
1
disallow_negation :to_not
-
end
-
-
# @private
-
1
class AnyInstanceExpectationTarget < TargetBase
-
1
EXPRESSION = :expect_any_instance_of
-
1
delegate_to :setup_any_instance_expectation
-
1
delegate_not_to :setup_any_instance_negative_expectation, :from => :not_to
-
1
delegate_not_to :setup_any_instance_negative_expectation, :from => :to_not
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Implements the methods needed for a pure test double. RSpec::Mocks::Double
-
# includes this module, and it is provided for cases where you want a
-
# pure test double without subclassing RSpec::Mocks::Double.
-
1
module TestDouble
-
# Creates a new test double with a `name` (that will be used in error
-
# messages only)
-
1
def initialize(name=nil, stubs={})
-
@__expired = false
-
if Hash === name && stubs.empty?
-
stubs = name
-
@name = nil
-
else
-
@name = name
-
end
-
assign_stubs(stubs)
-
end
-
-
# Tells the object to respond to all messages. If specific stub values
-
# are declared, they'll work as expected. If not, the receiver is
-
# returned.
-
1
def as_null_object
-
__mock_proxy.as_null_object
-
end
-
-
# Returns true if this object has received `as_null_object`
-
1
def null_object?
-
__mock_proxy.null_object?
-
end
-
-
# This allows for comparing the mock to other objects that proxy such as
-
# ActiveRecords belongs_to proxy objects. By making the other object run
-
# the comparison, we're sure the call gets delegated to the proxy
-
# target.
-
1
def ==(other)
-
other == __mock_proxy
-
end
-
-
# @private
-
1
def inspect
-
"#<#{self.class}:#{'0x%x' % object_id} @name=#{@name.inspect}>"
-
end
-
-
# @private
-
1
def to_s
-
inspect.gsub('<', '[').gsub('>', ']')
-
end
-
-
# @private
-
1
def respond_to?(message, incl_private=false)
-
__mock_proxy.null_object? ? true : super
-
end
-
-
# @private
-
1
def __build_mock_proxy_unless_expired(order_group)
-
__raise_expired_error || __build_mock_proxy(order_group)
-
end
-
-
# @private
-
1
def __disallow_further_usage!
-
@__expired = true
-
end
-
-
# Override for default freeze implementation to prevent freezing of test
-
# doubles.
-
1
def freeze
-
RSpec.warn_with("WARNING: you attempted to freeze a test double. This is explicitly a no-op as freezing doubles can lead to undesired behaviour when resetting tests.")
-
end
-
-
1
private
-
-
1
def method_missing(message, *args, &block)
-
proxy = __mock_proxy
-
proxy.record_message_received(message, *args, &block)
-
-
if proxy.null_object?
-
case message
-
when :to_int then return 0
-
when :to_a, :to_ary then return nil
-
when :to_str then return to_s
-
else return self
-
end
-
end
-
-
# Defined private and protected methods will still trigger `method_missing`
-
# when called publicly. We want ruby's method visibility error to get raised,
-
# so we simply delegate to `super` in that case.
-
# ...well, we would delegate to `super`, but there's a JRuby
-
# bug, so we raise our own visibility error instead:
-
# https://github.com/jruby/jruby/issues/1398
-
visibility = proxy.visibility_for(message)
-
if visibility == :private || visibility == :protected
-
ErrorGenerator.new(self, @name).raise_non_public_error(
-
message, visibility
-
)
-
end
-
-
# Required wrapping doubles in an Array on Ruby 1.9.2
-
raise NoMethodError if [:to_a, :to_ary].include? message
-
proxy.raise_unexpected_message_error(message, *args)
-
end
-
-
1
def assign_stubs(stubs)
-
stubs.each_pair do |message, response|
-
__mock_proxy.add_simple_stub(message, response)
-
end
-
end
-
-
1
def __mock_proxy
-
::RSpec::Mocks.space.proxy_for(self)
-
end
-
-
1
def __build_mock_proxy(order_group)
-
TestDoubleProxy.new(self, order_group, @name)
-
end
-
-
1
def __raise_expired_error
-
return false unless @__expired
-
ErrorGenerator.new(self, @name).raise_expired_test_double_error
-
end
-
-
1
def initialize_copy(other)
-
as_null_object if other.null_object?
-
super
-
end
-
end
-
-
# A generic test double object. `double`, `instance_double` and friends
-
# return an instance of this.
-
1
class Double
-
1
include TestDouble
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'verifying_proxy'
-
1
require 'stringio'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
module VerifyingDouble
-
1
def respond_to?(message, include_private=false)
-
return super unless null_object?
-
-
method_ref = __mock_proxy.method_reference[message]
-
-
case method_ref.visibility
-
when :public then true
-
when :private then include_private
-
when :protected then include_private || RUBY_VERSION.to_f < 2.0
-
else !method_ref.unimplemented?
-
end
-
end
-
-
1
def method_missing(message, *args, &block)
-
# Null object conditional is an optimization. If not a null object,
-
# validity of method expectations will have been checked at definition
-
# time.
-
if null_object?
-
if @__sending_message == message
-
__mock_proxy.ensure_implemented(message)
-
else
-
__mock_proxy.ensure_publicly_implemented(message, self)
-
end
-
end
-
-
super
-
end
-
-
# Redefining `__send__` causes ruby to issue a warning.
-
1
old, $stderr = $stderr, StringIO.new
-
1
def __send__(name, *args, &block)
-
@__sending_message = name
-
super
-
ensure
-
@__sending_message = nil
-
end
-
1
$stderr = old
-
-
1
def send(name, *args, &block)
-
__send__(name, *args, &block)
-
end
-
-
1
def initialize(*args)
-
super
-
@__sending_message = nil
-
end
-
end
-
-
# A mock providing a custom proxy that can verify the validity of any
-
# method stubs or expectations against the public instance methods of the
-
# given class.
-
#
-
# @private
-
1
class InstanceVerifyingDouble
-
1
include TestDouble
-
1
include VerifyingDouble
-
-
1
def initialize(doubled_module, *args)
-
@doubled_module = doubled_module
-
-
super(
-
"#{doubled_module.description} (instance)",
-
*args
-
)
-
end
-
-
1
def __build_mock_proxy(order_group)
-
VerifyingProxy.new(self, order_group, @name,
-
@doubled_module,
-
InstanceMethodReference
-
)
-
end
-
end
-
-
# An awkward module necessary because we cannot otherwise have
-
# ClassVerifyingDouble inherit from Module and still share these methods.
-
#
-
# @private
-
1
module ObjectVerifyingDoubleMethods
-
1
include TestDouble
-
1
include VerifyingDouble
-
-
1
def as_stubbed_const(options={})
-
ConstantMutator.stub(@doubled_module.const_to_replace, self, options)
-
self
-
end
-
-
1
private
-
-
1
def initialize(doubled_module, *args)
-
@doubled_module = doubled_module
-
super(doubled_module.description, *args)
-
end
-
-
1
def __build_mock_proxy(order_group)
-
VerifyingProxy.new(self, order_group, @name,
-
@doubled_module,
-
ObjectMethodReference
-
)
-
end
-
end
-
-
# Similar to an InstanceVerifyingDouble, except that it verifies against
-
# public methods of the given object.
-
#
-
# @private
-
1
class ObjectVerifyingDouble
-
1
include ObjectVerifyingDoubleMethods
-
end
-
-
# Effectively the same as an ObjectVerifyingDouble (since a class is a type
-
# of object), except with Module in the inheritance chain so that
-
# transferring nested constants to work.
-
#
-
# @private
-
1
class ClassVerifyingDouble < Module
-
1
include ObjectVerifyingDoubleMethods
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'method_signature_verifier'
-
-
1
module RSpec
-
1
module Mocks
-
# A message expectation that knows about the real implementation of the
-
# message being expected, so that it can verify that any expectations
-
# have the valid arguments.
-
# @api private
-
1
class VerifyingMessageExpectation < MessageExpectation
-
# A level of indirection is used here rather than just passing in the
-
# method itself, since method look up is expensive and we only want to
-
# do it if actually needed.
-
#
-
# Conceptually the method reference makes more sense as a constructor
-
# argument since it should be immutable, but it is significantly more
-
# straight forward to build the object in pieces so for now it stays as
-
# an accessor.
-
1
attr_accessor :method_reference
-
-
1
def initialize(*args)
-
super
-
end
-
-
# @private
-
1
def with(*args, &block)
-
unless ArgumentMatchers::AnyArgsMatcher === args.first
-
expected_args = if ArgumentMatchers::NoArgsMatcher === args.first
-
[]
-
elsif args.length > 0
-
args
-
else
-
# No arguments given, this will raise.
-
super
-
end
-
-
validate_expected_arguments!(expected_args)
-
end
-
super
-
end
-
-
1
private
-
-
1
def validate_expected_arguments!(actual_args)
-
return if method_reference.nil?
-
-
method_reference.with_signature do |signature|
-
verifier = Support::LooseSignatureVerifier.new(
-
signature,
-
actual_args
-
)
-
-
unless verifier.valid?
-
# Fail fast is required, otherwise the message expecation will fail
-
# as well ("expected method not called") and clobber this one.
-
@failed_fast = true
-
@error_generator.raise_invalid_arguments_error(verifier)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'verifying_message_expecation'
-
1
RSpec::Support.require_rspec_mocks 'method_reference'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
module VerifyingProxyMethods
-
1
def add_stub(method_name, opts={}, &implementation)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def add_simple_stub(method_name, *args)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def add_message_expectation(method_name, opts={}, &block)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def ensure_implemented(method_name)
-
return unless method_reference[method_name].unimplemented?
-
-
@error_generator.raise_unimplemented_error(
-
@doubled_module,
-
method_name
-
)
-
end
-
-
1
def ensure_publicly_implemented(method_name, _object)
-
ensure_implemented(method_name)
-
visibility = method_reference[method_name].visibility
-
-
return if visibility == :public
-
@error_generator.raise_non_public_error(method_name, visibility)
-
end
-
end
-
-
# A verifying proxy mostly acts like a normal proxy, except that it
-
# contains extra logic to try and determine the validity of any expectation
-
# set on it. This includes whether or not methods have been defined and the
-
# validatiy of arguments on method calls.
-
#
-
# In all other ways this behaves like a normal proxy. It only adds the
-
# verification behaviour to specific methods then delegates to the parent
-
# implementation.
-
#
-
# These checks are only activated if the doubled class has already been
-
# loaded, otherwise they are disabled. This allows for testing in
-
# isolation.
-
#
-
# @private
-
1
class VerifyingProxy < TestDoubleProxy
-
1
include VerifyingProxyMethods
-
-
1
def initialize(object, order_group, name, doubled_module, method_reference_class)
-
super(object, order_group, name)
-
@object = object
-
@doubled_module = doubled_module
-
@method_reference_class = method_reference_class
-
-
# A custom method double is required to pass through a way to lookup
-
# methods to determine their parameters. This is only relevant if the doubled
-
# class is loaded.
-
@method_doubles = Hash.new do |h, k|
-
h[k] = VerifyingMethodDouble.new(@object, k, self, method_reference[k])
-
end
-
end
-
-
1
def method_reference
-
@method_reference ||= Hash.new do |h, k|
-
h[k] = @method_reference_class.new(@doubled_module, k)
-
end
-
end
-
-
1
def visibility_for(method_name)
-
method_reference[method_name].visibility
-
end
-
end
-
-
# @private
-
1
class VerifyingPartialDoubleProxy < PartialDoubleProxy
-
1
include VerifyingProxyMethods
-
-
1
def initialize(object, expectation_ordering)
-
super(object, expectation_ordering)
-
@doubled_module = DirectObjectReference.new(object)
-
-
# A custom method double is required to pass through a way to lookup
-
# methods to determine their parameters.
-
@method_doubles = Hash.new do |h, k|
-
h[k] = VerifyingExistingMethodDouble.new(object, k, self)
-
end
-
end
-
-
1
def method_reference
-
@method_doubles
-
end
-
end
-
-
# @private
-
1
class VerifyingPartialClassDoubleProxy < VerifyingPartialDoubleProxy
-
1
include PartialClassDoubleProxyMethods
-
end
-
-
# @private
-
1
class VerifyingMethodDouble < MethodDouble
-
1
def initialize(object, method_name, proxy, method_reference)
-
super(object, method_name, proxy)
-
@method_reference = method_reference
-
end
-
-
1
def message_expectation_class
-
VerifyingMessageExpectation
-
end
-
-
1
def add_expectation(*args, &block)
-
# explict params necessary for 1.8.7 see #626
-
super(*args, &block).tap { |x| x.method_reference = @method_reference }
-
end
-
-
1
def add_stub(*args, &block)
-
# explict params necessary for 1.8.7 see #626
-
super(*args, &block).tap { |x| x.method_reference = @method_reference }
-
end
-
-
1
def proxy_method_invoked(obj, *args, &block)
-
validate_arguments!(args)
-
super
-
end
-
-
1
private
-
-
1
def validate_arguments!(actual_args)
-
@method_reference.with_signature do |signature|
-
verifier = Support::StrictSignatureVerifier.new(signature, actual_args)
-
raise ArgumentError, verifier.error_message unless verifier.valid?
-
end
-
end
-
end
-
-
# A VerifyingMethodDouble fetches the method to verify against from the
-
# original object, using a MethodReference. This works for pure doubles,
-
# but when the original object is itself the one being modified we need to
-
# collapse the reference and the method double into a single object so that
-
# we can access the original pristine method definition.
-
#
-
# @private
-
1
class VerifyingExistingMethodDouble < VerifyingMethodDouble
-
1
def initialize(object, method_name, proxy)
-
super(object, method_name, proxy, self)
-
-
@valid_method = object.respond_to?(method_name, true)
-
-
# Trigger an eager find of the original method since if we find it any
-
# later we end up getting a stubbed method with incorrect arity.
-
save_original_method!
-
end
-
-
1
def with_signature
-
yield Support::MethodSignature.new(original_method)
-
end
-
-
1
def unimplemented?
-
!@valid_method
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Version information for RSpec mocks.
-
1
module Version
-
# Version of RSpec mocks currently in use in SemVer format.
-
1
STRING = '3.1.3'
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'encoded_string'
-
1
RSpec::Support.require_rspec_support 'hunk_generator'
-
-
1
require 'pp'
-
-
1
module RSpec
-
1
module Support
-
# rubocop:disable ClassLength
-
1
class Differ
-
1
def diff(actual, expected)
-
diff = ""
-
-
if actual && expected
-
if all_strings?(actual, expected)
-
if any_multiline_strings?(actual, expected)
-
diff = diff_as_string(coerce_to_string(actual), coerce_to_string(expected))
-
end
-
elsif no_procs?(actual, expected) && no_numbers?(actual, expected)
-
diff = diff_as_object(actual, expected)
-
end
-
end
-
-
diff.to_s
-
end
-
-
# rubocop:disable MethodLength
-
1
def diff_as_string(actual, expected)
-
@encoding = pick_encoding actual, expected
-
-
@actual = EncodedString.new(actual, @encoding)
-
@expected = EncodedString.new(expected, @encoding)
-
-
output = EncodedString.new("\n", @encoding)
-
-
hunks.each_cons(2) do |prev_hunk, current_hunk|
-
begin
-
if current_hunk.overlaps?(prev_hunk)
-
add_old_hunk_to_hunk(current_hunk, prev_hunk)
-
else
-
add_to_output(output, prev_hunk.diff(format_type).to_s)
-
end
-
ensure
-
add_to_output(output, "\n")
-
end
-
end
-
-
finalize_output(output, hunks.last.diff(format_type).to_s) if hunks.last
-
-
color_diff output
-
rescue Encoding::CompatibilityError
-
handle_encoding_errors
-
end
-
# rubocop:enable MethodLength
-
-
1
def diff_as_object(actual, expected)
-
actual_as_string = object_to_string(actual)
-
expected_as_string = object_to_string(expected)
-
diff_as_string(actual_as_string, expected_as_string)
-
end
-
-
1
attr_reader :color
-
1
alias_method :color?, :color
-
-
1
def initialize(opts={})
-
@color = opts.fetch(:color, false)
-
@object_preparer = opts.fetch(:object_preparer, lambda { |string| string })
-
end
-
-
1
private
-
-
1
def no_procs?(*args)
-
safely_flatten(args).none? { |a| Proc === a }
-
end
-
-
1
def all_strings?(*args)
-
safely_flatten(args).all? { |a| String === a }
-
end
-
-
1
def any_multiline_strings?(*args)
-
all_strings?(*args) && safely_flatten(args).any? { |a| multiline?(a) }
-
end
-
-
1
def no_numbers?(*args)
-
safely_flatten(args).none? { |a| Numeric === a }
-
end
-
-
1
def coerce_to_string(string_or_array)
-
return string_or_array unless Array === string_or_array
-
diffably_stringify(string_or_array).join("\n")
-
end
-
-
1
def diffably_stringify(array)
-
array.map do |entry|
-
if Array === entry
-
entry.inspect
-
else
-
entry.to_s.gsub("\n", "\\n")
-
end
-
end
-
end
-
-
1
if String.method_defined?(:encoding)
-
1
def multiline?(string)
-
string.include?("\n".encode(string.encoding))
-
end
-
else
-
def multiline?(string)
-
string.include?("\n")
-
end
-
end
-
-
1
def hunks
-
@hunks ||= HunkGenerator.new(@actual, @expected).hunks
-
end
-
-
1
def finalize_output(output, final_line)
-
add_to_output(output, final_line)
-
add_to_output(output, "\n")
-
end
-
-
1
def add_to_output(output, string)
-
output << string
-
end
-
-
1
def add_old_hunk_to_hunk(hunk, oldhunk)
-
hunk.merge(oldhunk)
-
end
-
-
1
def safely_flatten(array)
-
array = array.flatten(1) until (array == array.flatten(1))
-
array
-
end
-
-
1
def format_type
-
:unified
-
end
-
-
1
def color(text, color_code)
-
"\e[#{color_code}m#{text}\e[0m"
-
end
-
-
1
def red(text)
-
color(text, 31)
-
end
-
-
1
def green(text)
-
color(text, 32)
-
end
-
-
1
def blue(text)
-
color(text, 34)
-
end
-
-
1
def normal(text)
-
color(text, 0)
-
end
-
-
1
def color_diff(diff)
-
return diff unless color?
-
-
diff.lines.map do |line|
-
case line[0].chr
-
when "+"
-
green line
-
when "-"
-
red line
-
when "@"
-
line[1].chr == "@" ? blue(line) : normal(line)
-
else
-
normal(line)
-
end
-
end.join
-
end
-
-
1
def object_to_string(object)
-
object = @object_preparer.call(object)
-
case object
-
when Hash
-
object.keys.sort_by { |k| k.to_s }.map do |key|
-
pp_key = PP.singleline_pp(key, "")
-
pp_value = PP.singleline_pp(object[key], "")
-
-
"#{pp_key} => #{pp_value},"
-
end.join("\n")
-
when String
-
object =~ /\n/ ? object : object.inspect
-
else
-
PP.pp(object, "")
-
end
-
end
-
-
1
if String.method_defined?(:encoding)
-
1
def pick_encoding(source_a, source_b)
-
Encoding.compatible?(source_a, source_b) || Encoding.default_external
-
end
-
else
-
def pick_encoding(_source_a, _source_b)
-
end
-
end
-
-
1
def handle_encoding_errors
-
if @actual.source_encoding != @expected.source_encoding
-
"Could not produce a diff because the encoding of the actual string " \
-
"(#{@actual.source_encoding}) differs from the encoding of the expected " \
-
"string (#{@expected.source_encoding})"
-
else
-
"Could not produce a diff because of the encoding of the string " \
-
"(#{@expected.source_encoding})"
-
end
-
end
-
end
-
# rubocop:enable ClassLength
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Provides a means to fuzzy-match between two arbitrary objects.
-
# Understands array/hash nesting. Uses `===` or `==` to
-
# perform the matching.
-
1
module FuzzyMatcher
-
# @api private
-
1
def self.values_match?(expected, actual)
-
21
if Array === expected && Enumerable === actual && !(Struct === actual)
-
return arrays_match?(expected, actual.to_a)
-
elsif Hash === expected && Hash === actual
-
return hashes_match?(expected, actual)
-
elsif actual == expected
-
return true
-
end
-
-
21
begin
-
21
expected === actual
-
rescue ArgumentError
-
# Some objects, like 0-arg lambdas on 1.9+, raise
-
# ArgumentError for `expected === actual`.
-
false
-
end
-
end
-
-
# @private
-
1
def self.arrays_match?(expected_list, actual_list)
-
return false if expected_list.size != actual_list.size
-
-
expected_list.zip(actual_list).all? do |expected, actual|
-
values_match?(expected, actual)
-
end
-
end
-
-
# @private
-
1
def self.hashes_match?(expected_hash, actual_hash)
-
return false if expected_hash.size != actual_hash.size
-
-
expected_hash.all? do |expected_key, expected_value|
-
actual_value = actual_hash.fetch(expected_key) { return false }
-
values_match?(expected_value, actual_value)
-
end
-
end
-
-
1
private_class_method :arrays_match?, :hashes_match?
-
end
-
end
-
end
-
1
require 'diff/lcs'
-
1
require 'diff/lcs/hunk'
-
-
1
module RSpec
-
1
module Support
-
# @private
-
1
class HunkGenerator
-
1
def initialize(actual, expected)
-
@actual = actual
-
@expected = expected
-
end
-
-
1
def hunks
-
@file_length_difference = 0
-
@hunks ||= diffs.map do |piece|
-
build_hunk(piece)
-
end
-
end
-
-
1
private
-
-
1
def diffs
-
Diff::LCS.diff(expected_lines, actual_lines)
-
end
-
-
1
def expected_lines
-
@expected.split("\n").map! { |e| e.chomp }
-
end
-
-
1
def actual_lines
-
@actual.split("\n").map! { |e| e.chomp }
-
end
-
-
1
def build_hunk(piece)
-
Diff::LCS::Hunk.new(
-
expected_lines, actual_lines, piece, context_lines, @file_length_difference
-
).tap do |h|
-
@file_length_difference = h.file_length_difference
-
end
-
end
-
-
1
def context_lines
-
3
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# @private
-
1
def self.matcher_definitions
-
2
@matcher_definitions ||= []
-
end
-
-
# Used internally to break cyclic dependency between mocks, expectations,
-
# and support. We don't currently have a consistent implementation of our
-
# matchers, though we are considering changing that:
-
# https://github.com/rspec/rspec-mocks/issues/513
-
#
-
# @private
-
1
def self.register_matcher_definition(&block)
-
2
matcher_definitions << block
-
end
-
-
# Remove a previously registered matcher. Useful for cleaning up after
-
# yourself in specs.
-
#
-
# @private
-
1
def self.deregister_matcher_definition(&block)
-
matcher_definitions.delete(block)
-
end
-
-
# @private
-
1
def self.is_a_matcher?(object)
-
matcher_definitions.any? { |md| md.call(object) }
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "ruby_features"
-
1
RSpec::Support.require_rspec_support "matcher_definition"
-
-
1
module RSpec
-
1
module Support
-
# Extracts info about the number of arguments and allowed/required
-
# keyword args of a given method.
-
#
-
# @private
-
1
class MethodSignature
-
1
attr_reader :min_non_kw_args, :max_non_kw_args
-
-
1
def initialize(method)
-
@method = method
-
classify_parameters
-
end
-
-
1
def non_kw_args_arity_description
-
case max_non_kw_args
-
when min_non_kw_args then min_non_kw_args.to_s
-
when INFINITY then "#{min_non_kw_args} or more"
-
else "#{min_non_kw_args} to #{max_non_kw_args}"
-
end
-
end
-
-
1
def valid_non_kw_args?(positional_arg_count)
-
min_non_kw_args <= positional_arg_count &&
-
positional_arg_count <= max_non_kw_args
-
end
-
-
1
if RubyFeatures.optional_and_splat_args_supported?
-
1
def description
-
@description ||= begin
-
parts = []
-
-
unless non_kw_args_arity_description == "0"
-
parts << "arity of #{non_kw_args_arity_description}"
-
end
-
-
if @optional_kw_args.any?
-
parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})"
-
end
-
-
if @required_kw_args.any?
-
parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})"
-
end
-
-
parts << "any additional keyword args" if @allows_any_kw_args
-
-
parts.join(" and ")
-
end
-
end
-
-
1
def missing_kw_args_from(given_kw_args)
-
@required_kw_args - given_kw_args
-
end
-
-
1
def invalid_kw_args_from(given_kw_args)
-
return [] if @allows_any_kw_args
-
given_kw_args - @allowed_kw_args
-
end
-
-
1
def has_kw_args_in?(args)
-
Hash === args.last && could_contain_kw_args?(args)
-
end
-
-
# Without considering what the last arg is, could it
-
# contain keyword arguments?
-
1
def could_contain_kw_args?(args)
-
return false if args.count <= min_non_kw_args
-
@allows_any_kw_args || @allowed_kw_args.any?
-
end
-
-
1
def classify_parameters
-
optional_non_kw_args = @min_non_kw_args = 0
-
@optional_kw_args, @required_kw_args = [], []
-
@allows_any_kw_args = false
-
-
@method.parameters.each do |(type, name)|
-
case type
-
# def foo(a:)
-
when :keyreq then @required_kw_args << name
-
# def foo(a: 1)
-
when :key then @optional_kw_args << name
-
# def foo(**kw_args)
-
when :keyrest then @allows_any_kw_args = true
-
# def foo(a)
-
when :req then @min_non_kw_args += 1
-
# def foo(a = 1)
-
when :opt then optional_non_kw_args += 1
-
# def foo(*a)
-
when :rest then optional_non_kw_args = INFINITY
-
end
-
end
-
-
@max_non_kw_args = @min_non_kw_args + optional_non_kw_args
-
@allowed_kw_args = @required_kw_args + @optional_kw_args
-
end
-
else
-
def description
-
"arity of #{non_kw_args_arity_description}"
-
end
-
-
def missing_kw_args_from(_given_kw_args)
-
[]
-
end
-
-
def invalid_kw_args_from(_given_kw_args)
-
[]
-
end
-
-
def has_kw_args_in?(_args)
-
false
-
end
-
-
def could_contain_kw_args?(*)
-
false
-
end
-
-
def classify_parameters
-
arity = @method.arity
-
if arity < 0
-
# `~` inverts the one's complement and gives us the
-
# number of required args
-
@min_non_kw_args = ~arity
-
@max_non_kw_args = INFINITY
-
else
-
@min_non_kw_args = arity
-
@max_non_kw_args = arity
-
end
-
end
-
end
-
-
1
INFINITY = 1 / 0.0
-
end
-
-
# Deals with the slightly different semantics of block arguments.
-
# For methods, arguments are required unless a default value is provided.
-
# For blocks, arguments are optional, even if no default value is provided.
-
#
-
# However, we want to treat block args as required since you virtually
-
# always want to pass a value for each received argument and our
-
# `and_yield` has treated block args as required for many years.
-
#
-
# @api private
-
1
class BlockSignature < MethodSignature
-
1
if RubyFeatures.optional_and_splat_args_supported?
-
1
def classify_parameters
-
super
-
@min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY
-
end
-
end
-
end
-
-
# Abstract base class for signature verifiers.
-
#
-
# @api private
-
1
class MethodSignatureVerifier
-
1
attr_reader :non_kw_args, :kw_args
-
-
1
def initialize(signature, args)
-
@signature = signature
-
@non_kw_args, @kw_args = split_args(*args)
-
end
-
-
1
def valid?
-
missing_kw_args.empty? &&
-
invalid_kw_args.empty? &&
-
valid_non_kw_args?
-
end
-
-
1
def error_message
-
if missing_kw_args.any?
-
"Missing required keyword arguments: %s" % [
-
missing_kw_args.join(", ")
-
]
-
elsif invalid_kw_args.any?
-
"Invalid keyword arguments provided: %s" % [
-
invalid_kw_args.join(", ")
-
]
-
elsif !valid_non_kw_args?
-
"Wrong number of arguments. Expected %s, got %s." % [
-
@signature.non_kw_args_arity_description,
-
non_kw_args.length
-
]
-
end
-
end
-
-
1
private
-
-
1
def valid_non_kw_args?
-
@signature.valid_non_kw_args?(non_kw_args.length)
-
end
-
-
1
def missing_kw_args
-
@signature.missing_kw_args_from(kw_args)
-
end
-
-
1
def invalid_kw_args
-
@signature.invalid_kw_args_from(kw_args)
-
end
-
-
1
def split_args(*args)
-
kw_args = if @signature.has_kw_args_in?(args)
-
args.pop.keys
-
else
-
[]
-
end
-
-
[args, kw_args]
-
end
-
end
-
-
# Figures out wether a given method can accept various arguments.
-
# Surprisingly non-trivial.
-
#
-
# @private
-
1
StrictSignatureVerifier = MethodSignatureVerifier
-
-
# Allows matchers to be used instead of providing keyword arguments. In
-
# practice, when this happens only the arity of the method is verified.
-
#
-
# @private
-
1
class LooseSignatureVerifier < MethodSignatureVerifier
-
1
private
-
-
1
def split_args(*args)
-
if RSpec::Support.is_a_matcher?(args.last) && @signature.could_contain_kw_args?(args)
-
args.pop
-
@signature = SignatureWithKeywordArgumentsMatcher.new(@signature)
-
end
-
-
super(*args)
-
end
-
-
# If a matcher is used in a signature in place of keyword arguments, all
-
# keyword argument validation needs to be skipped since the matcher is
-
# opaque.
-
#
-
# Instead, keyword arguments will be validated when the method is called
-
# and they are actually known.
-
#
-
# @private
-
1
class SignatureWithKeywordArgumentsMatcher
-
1
def initialize(signature)
-
@signature = signature
-
end
-
-
1
def missing_kw_args_from(_kw_args)
-
[]
-
end
-
-
1
def invalid_kw_args_from(_kw_args)
-
[]
-
end
-
-
1
def non_kw_args_arity_description
-
@signature.non_kw_args_arity_description
-
end
-
-
1
def valid_non_kw_args?(*args)
-
@signature.valid_non_kw_args?(*args)
-
end
-
-
1
def has_kw_args_in?(args)
-
@signature.has_kw_args_in?(args)
-
end
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Deep
-
1
def self.freeze(object)
-
14
object.each do |*entry|
-
31
value = entry.last
-
31
case value
-
when String, Regexp
-
7
value.freeze
-
when Enumerable
-
11
Deep.freeze(value)
-
end
-
end
-
-
14
return object.freeze
-
end
-
-
1
def self.copy(object)
-
7
duplicate = object.dup rescue object
-
-
7
case object
-
when Array
-
1
(0...duplicate.count).each do |i|
-
duplicate[i] = Deep.copy(duplicate[i])
-
end
-
when Hash
-
2
duplicate.keys.each do |key|
-
6
duplicate[key] = Deep.copy(duplicate[key])
-
end
-
end
-
-
7
duplicate
-
end
-
end
-
end
-
1
require "set"
-
-
1
module SafeYAML
-
1
class LibyamlChecker
-
1
LIBYAML_VERSION = Psych::LIBYAML_VERSION rescue nil
-
-
# Do proper version comparison (e.g. so 0.1.10 is >= 0.1.6)
-
1
SAFE_LIBYAML_VERSION = Gem::Version.new("0.1.6")
-
-
1
KNOWN_PATCHED_LIBYAML_VERSIONS = Set.new([
-
# http://people.canonical.com/~ubuntu-security/cve/2014/CVE-2014-2525.html
-
"0.1.4-2ubuntu0.12.04.3",
-
"0.1.4-2ubuntu0.12.10.3",
-
"0.1.4-2ubuntu0.13.10.3",
-
"0.1.4-3ubuntu3",
-
-
# https://security-tracker.debian.org/tracker/CVE-2014-2525
-
"0.1.3-1+deb6u4",
-
"0.1.4-2+deb7u4",
-
"0.1.4-3.2"
-
]).freeze
-
-
1
def self.libyaml_version_ok?
-
return true if YAML_ENGINE != "psych" || defined?(JRUBY_VERSION)
-
return true if Gem::Version.new(LIBYAML_VERSION || "0") >= SAFE_LIBYAML_VERSION
-
return libyaml_patched?
-
end
-
-
1
def self.libyaml_patched?
-
return false if (`which dpkg` rescue '').empty?
-
libyaml_version = `dpkg -s libyaml-0-2`.match(/^Version: (.*)$/)
-
return false if libyaml_version.nil?
-
KNOWN_PATCHED_LIBYAML_VERSIONS.include?(libyaml_version[1])
-
end
-
end
-
end
-
1
require "set"
-
1
require "yaml"
-
-
# This needs to be defined up front in case any internal classes need to base
-
# their behavior off of this.
-
1
module SafeYAML
-
1
YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : (defined?(Psych) && YAML == Psych ? "psych" : "syck")
-
end
-
-
1
require "safe_yaml/libyaml_checker"
-
1
require "safe_yaml/deep"
-
1
require "safe_yaml/parse/hexadecimal"
-
1
require "safe_yaml/parse/sexagesimal"
-
1
require "safe_yaml/parse/date"
-
1
require "safe_yaml/transform/transformation_map"
-
1
require "safe_yaml/transform/to_boolean"
-
1
require "safe_yaml/transform/to_date"
-
1
require "safe_yaml/transform/to_float"
-
1
require "safe_yaml/transform/to_integer"
-
1
require "safe_yaml/transform/to_nil"
-
1
require "safe_yaml/transform/to_symbol"
-
1
require "safe_yaml/transform"
-
1
require "safe_yaml/resolver"
-
1
require "safe_yaml/syck_hack" if SafeYAML::YAML_ENGINE == "syck" && defined?(JRUBY_VERSION)
-
-
1
module SafeYAML
-
1
MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1
-
-
1
DEFAULT_OPTIONS = Deep.freeze({
-
:default_mode => nil,
-
:suppress_warnings => false,
-
:deserialize_symbols => false,
-
:whitelisted_tags => [],
-
:custom_initializers => {},
-
:raise_on_unknown_tag => false
-
})
-
-
1
OPTIONS = Deep.copy(DEFAULT_OPTIONS)
-
-
1
PREDEFINED_TAGS = {}
-
-
1
if YAML_ENGINE == "syck"
-
YAML.tagged_classes.each do |tag, klass|
-
PREDEFINED_TAGS[klass] = tag
-
end
-
-
else
-
# Special tags appear to be hard-coded in Psych:
-
# https://github.com/tenderlove/psych/blob/v1.3.4/lib/psych/visitors/to_ruby.rb
-
# Fortunately, there aren't many that SafeYAML doesn't already support.
-
1
PREDEFINED_TAGS.merge!({
-
Exception => "!ruby/exception",
-
Range => "!ruby/range",
-
Regexp => "!ruby/regexp",
-
})
-
end
-
-
1
Deep.freeze(PREDEFINED_TAGS)
-
-
1
module_function
-
-
1
def restore_defaults!
-
OPTIONS.clear.merge!(Deep.copy(DEFAULT_OPTIONS))
-
end
-
-
1
def tag_safety_check!(tag, options)
-
return if tag.nil? || tag == "!"
-
if options[:raise_on_unknown_tag] && !options[:whitelisted_tags].include?(tag) && !tag_is_explicitly_trusted?(tag)
-
raise "Unknown YAML tag '#{tag}'"
-
end
-
end
-
-
1
def whitelist!(*classes)
-
classes.each do |klass|
-
whitelist_class!(klass)
-
end
-
end
-
-
1
def whitelist_class!(klass)
-
raise "#{klass} not a Class" unless klass.is_a?(::Class)
-
-
klass_name = klass.name
-
raise "#{klass} cannot be anonymous" if klass_name.nil? || klass_name.empty?
-
-
# Whitelist any built-in YAML tags supplied by Syck or Psych.
-
predefined_tag = PREDEFINED_TAGS[klass]
-
if predefined_tag
-
OPTIONS[:whitelisted_tags] << predefined_tag
-
return
-
end
-
-
# Exception is exceptional (har har).
-
tag_class = klass < Exception ? "exception" : "object"
-
-
tag_prefix = case YAML_ENGINE
-
when "psych" then "!ruby/#{tag_class}"
-
when "syck" then "tag:ruby.yaml.org,2002:#{tag_class}"
-
else raise "unknown YAML_ENGINE #{YAML_ENGINE}"
-
end
-
OPTIONS[:whitelisted_tags] << "#{tag_prefix}:#{klass_name}"
-
end
-
-
1
if YAML_ENGINE == "psych"
-
1
def tag_is_explicitly_trusted?(tag)
-
false
-
end
-
-
else
-
TRUSTED_TAGS = Set.new([
-
"tag:yaml.org,2002:binary",
-
"tag:yaml.org,2002:bool#no",
-
"tag:yaml.org,2002:bool#yes",
-
"tag:yaml.org,2002:float",
-
"tag:yaml.org,2002:float#fix",
-
"tag:yaml.org,2002:int",
-
"tag:yaml.org,2002:map",
-
"tag:yaml.org,2002:null",
-
"tag:yaml.org,2002:seq",
-
"tag:yaml.org,2002:str",
-
"tag:yaml.org,2002:timestamp",
-
"tag:yaml.org,2002:timestamp#ymd"
-
]).freeze
-
-
def tag_is_explicitly_trusted?(tag)
-
TRUSTED_TAGS.include?(tag)
-
end
-
end
-
-
1
if SafeYAML::YAML_ENGINE == "psych"
-
1
require "safe_yaml/psych_handler"
-
1
require "safe_yaml/psych_resolver"
-
1
require "safe_yaml/safe_to_ruby_visitor"
-
-
1
def self.load(yaml, filename=nil, options={})
-
# If the user hasn't whitelisted any tags, we can go with this implementation which is
-
# significantly faster.
-
if (options && options[:whitelisted_tags] || SafeYAML::OPTIONS[:whitelisted_tags]).empty?
-
safe_handler = SafeYAML::PsychHandler.new(options) do |result|
-
return result
-
end
-
arguments_for_parse = [yaml]
-
arguments_for_parse << filename if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
-
Psych::Parser.new(safe_handler).parse(*arguments_for_parse)
-
return safe_handler.result
-
-
else
-
safe_resolver = SafeYAML::PsychResolver.new(options)
-
tree = SafeYAML::MULTI_ARGUMENT_YAML_LOAD ?
-
Psych.parse(yaml, filename) :
-
Psych.parse(yaml)
-
return safe_resolver.resolve_node(tree)
-
end
-
end
-
-
1
def self.load_file(filename, options={})
-
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
-
File.open(filename, 'r:bom|utf-8') { |f| self.load(f, filename, options) }
-
-
else
-
# Ruby pukes on 1.9.2 if we try to open an empty file w/ 'r:bom|utf-8';
-
# so we'll not specify those flags here. This mirrors the behavior for
-
# unsafe_load_file so it's probably preferable anyway.
-
self.load File.open(filename), nil, options
-
end
-
end
-
-
else
-
require "safe_yaml/syck_resolver"
-
require "safe_yaml/syck_node_monkeypatch"
-
-
def self.load(yaml, options={})
-
resolver = SafeYAML::SyckResolver.new(SafeYAML::OPTIONS.merge(options || {}))
-
tree = YAML.parse(yaml)
-
return resolver.resolve_node(tree)
-
end
-
-
def self.load_file(filename, options={})
-
File.open(filename) { |f| self.load(f, options) }
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Parse
-
1
class Date
-
# This one's easy enough :)
-
1
DATE_MATCHER = /\A(\d{4})-(\d{2})-(\d{2})\Z/.freeze
-
-
# This unbelievable little gem is taken basically straight from the YAML spec, but made
-
# slightly more readable (to my poor eyes at least) to me:
-
# http://yaml.org/type/timestamp.html
-
1
TIME_MATCHER = /\A\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|\s+)\d{1,2}:\d{2}:\d{2}(?:\.\d*)?\s*(?:Z|[-+]\d{1,2}(?::?\d{2})?)?\Z/.freeze
-
-
1
SECONDS_PER_DAY = 60 * 60 * 24
-
1
MICROSECONDS_PER_SECOND = 1000000
-
-
# So this is weird. In Ruby 1.8.7, the DateTime#sec_fraction method returned fractional
-
# seconds in units of DAYS for some reason. In 1.9.2, they changed the units -- much more
-
# reasonably -- to seconds.
-
1
SEC_FRACTION_MULTIPLIER = RUBY_VERSION == "1.8.7" ? (SECONDS_PER_DAY * MICROSECONDS_PER_SECOND) : MICROSECONDS_PER_SECOND
-
-
# The DateTime class has a #to_time method in Ruby 1.9+;
-
# Before that we'll just need to convert DateTime to Time ourselves.
-
1
TO_TIME_AVAILABLE = DateTime.instance_methods.include?(:to_time)
-
-
1
def self.value(value)
-
d = DateTime.parse(value)
-
-
return d.to_time if TO_TIME_AVAILABLE
-
-
usec = d.sec_fraction * SEC_FRACTION_MULTIPLIER
-
time = Time.utc(d.year, d.month, d.day, d.hour, d.min, d.sec, usec) - (d.offset * SECONDS_PER_DAY)
-
time.getlocal
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Parse
-
1
class Hexadecimal
-
1
MATCHER = /\A[-+]?0x[0-9a-fA-F_]+\Z/.freeze
-
-
1
def self.value(value)
-
# This is safe to do since we already validated the value.
-
return Integer(value.gsub(/_/, ""))
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Parse
-
1
class Sexagesimal
-
1
INTEGER_MATCHER = /\A[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\Z/.freeze
-
1
FLOAT_MATCHER = /\A[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]*\Z/.freeze
-
-
1
def self.value(value)
-
before_decimal, after_decimal = value.split(".")
-
-
whole_part = 0
-
multiplier = 1
-
-
before_decimal = before_decimal.split(":")
-
until before_decimal.empty?
-
whole_part += (Float(before_decimal.pop) * multiplier)
-
multiplier *= 60
-
end
-
-
result = whole_part
-
result += Float("." + after_decimal) unless after_decimal.nil?
-
result *= -1 if value[0] == "-"
-
result
-
end
-
end
-
end
-
end
-
1
require "psych"
-
1
require "base64"
-
-
1
module SafeYAML
-
1
class PsychHandler < Psych::Handler
-
1
def initialize(options, &block)
-
@options = SafeYAML::OPTIONS.merge(options || {})
-
@block = block
-
@initializers = @options[:custom_initializers] || {}
-
@anchors = {}
-
@stack = []
-
@current_key = nil
-
@result = nil
-
@begun = false
-
end
-
-
1
def result
-
@begun ? @result : false
-
end
-
-
1
def add_to_current_structure(value, anchor=nil, quoted=nil, tag=nil)
-
value = Transform.to_proper_type(value, quoted, tag, @options)
-
-
@anchors[anchor] = value if anchor
-
-
if !@begun
-
@begun = true
-
@result = value
-
@current_structure = @result
-
return
-
end
-
-
if @current_structure.respond_to?(:<<)
-
@current_structure << value
-
-
elsif @current_structure.respond_to?(:[]=)
-
if @current_key.nil?
-
@current_key = value
-
-
else
-
if @current_key == "<<"
-
@current_structure.merge!(value)
-
else
-
@current_structure[@current_key] = value
-
end
-
-
@current_key = nil
-
end
-
-
else
-
raise "Don't know how to add to a #{@current_structure.class}!"
-
end
-
end
-
-
1
def end_current_structure
-
@stack.pop
-
@current_structure = @stack.last
-
end
-
-
1
def streaming?
-
true
-
end
-
-
# event handlers
-
1
def alias(anchor)
-
add_to_current_structure(@anchors[anchor])
-
end
-
-
1
def scalar(value, anchor, tag, plain, quoted, style)
-
add_to_current_structure(value, anchor, quoted, tag)
-
end
-
-
1
def end_document(implicit)
-
@block.call(@result)
-
end
-
-
1
def start_mapping(anchor, tag, implicit, style)
-
map = @initializers.include?(tag) ? @initializers[tag].call : {}
-
self.add_to_current_structure(map, anchor)
-
@current_structure = map
-
@stack.push(map)
-
end
-
-
1
def end_mapping
-
self.end_current_structure()
-
end
-
-
1
def start_sequence(anchor, tag, implicit, style)
-
seq = @initializers.include?(tag) ? @initializers[tag].call : []
-
self.add_to_current_structure(seq, anchor)
-
@current_structure = seq
-
@stack.push(seq)
-
end
-
-
1
def end_sequence
-
self.end_current_structure()
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class PsychResolver < Resolver
-
1
NODE_TYPES = {
-
Psych::Nodes::Document => :root,
-
Psych::Nodes::Mapping => :map,
-
Psych::Nodes::Sequence => :seq,
-
Psych::Nodes::Scalar => :scalar,
-
Psych::Nodes::Alias => :alias
-
}.freeze
-
-
1
def initialize(options={})
-
super
-
@aliased_nodes = {}
-
end
-
-
1
def resolve_root(root)
-
resolve_seq(root).first
-
end
-
-
1
def resolve_alias(node)
-
resolve_node(@aliased_nodes[node.anchor])
-
end
-
-
1
def native_resolve(node)
-
@visitor ||= SafeYAML::SafeToRubyVisitor.new(self)
-
@visitor.accept(node)
-
end
-
-
1
def get_node_type(node)
-
NODE_TYPES[node.class]
-
end
-
-
1
def get_node_tag(node)
-
node.tag
-
end
-
-
1
def get_node_value(node)
-
@aliased_nodes[node.anchor] = node if node.respond_to?(:anchor) && node.anchor
-
-
case get_node_type(node)
-
when :root, :map, :seq
-
node.children
-
when :scalar
-
node.value
-
end
-
end
-
-
1
def value_is_quoted?(node)
-
node.quoted
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Resolver
-
1
def initialize(options)
-
@options = SafeYAML::OPTIONS.merge(options || {})
-
@whitelist = @options[:whitelisted_tags] || []
-
@initializers = @options[:custom_initializers] || {}
-
@raise_on_unknown_tag = @options[:raise_on_unknown_tag]
-
end
-
-
1
def resolve_node(node)
-
return node if !node
-
return self.native_resolve(node) if tag_is_whitelisted?(self.get_node_tag(node))
-
-
case self.get_node_type(node)
-
when :root
-
resolve_root(node)
-
when :map
-
resolve_map(node)
-
when :seq
-
resolve_seq(node)
-
when :scalar
-
resolve_scalar(node)
-
when :alias
-
resolve_alias(node)
-
else
-
raise "Don't know how to resolve this node: #{node.inspect}"
-
end
-
end
-
-
1
def resolve_map(node)
-
tag = get_and_check_node_tag(node)
-
hash = @initializers.include?(tag) ? @initializers[tag].call : {}
-
map = normalize_map(self.get_node_value(node))
-
-
# Take the "<<" key nodes first, as these are meant to approximate a form of inheritance.
-
inheritors = map.select { |key_node, value_node| resolve_node(key_node) == "<<" }
-
inheritors.each do |key_node, value_node|
-
merge_into_hash(hash, resolve_node(value_node))
-
end
-
-
# All that's left should be normal (non-"<<") nodes.
-
(map - inheritors).each do |key_node, value_node|
-
hash[resolve_node(key_node)] = resolve_node(value_node)
-
end
-
-
return hash
-
end
-
-
1
def resolve_seq(node)
-
seq = self.get_node_value(node)
-
-
tag = get_and_check_node_tag(node)
-
arr = @initializers.include?(tag) ? @initializers[tag].call : []
-
-
seq.inject(arr) { |array, n| array << resolve_node(n) }
-
end
-
-
1
def resolve_scalar(node)
-
Transform.to_proper_type(self.get_node_value(node), self.value_is_quoted?(node), get_and_check_node_tag(node), @options)
-
end
-
-
1
def get_and_check_node_tag(node)
-
tag = self.get_node_tag(node)
-
SafeYAML.tag_safety_check!(tag, @options)
-
tag
-
end
-
-
1
def tag_is_whitelisted?(tag)
-
@whitelist.include?(tag)
-
end
-
-
1
def options
-
@options
-
end
-
-
1
private
-
1
def normalize_map(map)
-
# Syck creates Hashes from maps.
-
if map.is_a?(Hash)
-
map.inject([]) { |arr, key_and_value| arr << key_and_value }
-
-
# Psych is really weird; it flattens out a Hash completely into: [key, value, key, value, ...]
-
else
-
map.each_slice(2).to_a
-
end
-
end
-
-
1
def merge_into_hash(hash, array)
-
array.each do |key, value|
-
hash[key] = value
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class SafeToRubyVisitor < Psych::Visitors::ToRuby
-
1
INITIALIZE_ARITY = superclass.instance_method(:initialize).arity
-
-
1
def initialize(resolver)
-
case INITIALIZE_ARITY
-
when 2
-
# https://github.com/tenderlove/psych/blob/v2.0.0/lib/psych/visitors/to_ruby.rb#L14-L28
-
loader = Psych::ClassLoader.new
-
scanner = Psych::ScalarScanner.new(loader)
-
super(scanner, loader)
-
-
else
-
super()
-
end
-
-
@resolver = resolver
-
end
-
-
1
def accept(node)
-
if node.tag
-
SafeYAML.tag_safety_check!(node.tag, @resolver.options)
-
return super
-
end
-
-
@resolver.resolve_node(node)
-
end
-
end
-
end
-
1
require 'base64'
-
-
1
module SafeYAML
-
1
class Transform
-
1
TRANSFORMERS = [
-
Transform::ToSymbol.new,
-
Transform::ToInteger.new,
-
Transform::ToFloat.new,
-
Transform::ToNil.new,
-
Transform::ToBoolean.new,
-
Transform::ToDate.new
-
]
-
-
1
def self.to_guessed_type(value, quoted=false, options=nil)
-
return value if quoted
-
-
if value.is_a?(String)
-
TRANSFORMERS.each do |transformer|
-
success, transformed_value = transformer.method(:transform?).arity == 1 ?
-
transformer.transform?(value) :
-
transformer.transform?(value, options)
-
-
return transformed_value if success
-
end
-
end
-
-
value
-
end
-
-
1
def self.to_proper_type(value, quoted=false, tag=nil, options=nil)
-
case tag
-
when "tag:yaml.org,2002:binary", "x-private:binary", "!binary"
-
decoded = Base64.decode64(value)
-
decoded = decoded.force_encoding(value.encoding) if decoded.respond_to?(:force_encoding)
-
decoded
-
else
-
self.to_guessed_type(value, quoted, options)
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Transform
-
1
class ToBoolean
-
1
include TransformationMap
-
-
1
set_predefined_values({
-
"yes" => true,
-
"on" => true,
-
"true" => true,
-
"no" => false,
-
"off" => false,
-
"false" => false
-
})
-
-
1
def transform?(value)
-
return false if value.length > 5
-
return PREDEFINED_VALUES.include?(value), PREDEFINED_VALUES[value]
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Transform
-
1
class ToDate
-
1
def transform?(value)
-
return true, Date.parse(value) if Parse::Date::DATE_MATCHER.match(value)
-
return true, Parse::Date.value(value) if Parse::Date::TIME_MATCHER.match(value)
-
false
-
rescue ArgumentError
-
return true, value
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Transform
-
1
class ToFloat
-
1
Infinity = 1.0 / 0.0
-
1
NaN = 0.0 / 0.0
-
-
1
PREDEFINED_VALUES = {
-
".inf" => Infinity,
-
".Inf" => Infinity,
-
".INF" => Infinity,
-
"-.inf" => -Infinity,
-
"-.Inf" => -Infinity,
-
"-.INF" => -Infinity,
-
".nan" => NaN,
-
".NaN" => NaN,
-
".NAN" => NaN,
-
}.freeze
-
-
1
MATCHER = /\A[-+]?(?:\d[\d_]*)?\.[\d_]+(?:[eE][-+][\d]+)?\Z/.freeze
-
-
1
def transform?(value)
-
return true, Float(value) if MATCHER.match(value)
-
try_edge_cases?(value)
-
end
-
-
1
def try_edge_cases?(value)
-
return true, PREDEFINED_VALUES[value] if PREDEFINED_VALUES.include?(value)
-
return true, Parse::Sexagesimal.value(value) if Parse::Sexagesimal::FLOAT_MATCHER.match(value)
-
return false
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Transform
-
1
class ToInteger
-
1
MATCHERS = Deep.freeze([
-
/\A[-+]?(0|([1-9][0-9_,]*))\Z/, # decimal
-
/\A0[0-7]+\Z/, # octal
-
/\A0x[0-9a-f]+\Z/i, # hexadecimal
-
/\A0b[01_]+\Z/ # binary
-
])
-
-
1
def transform?(value)
-
MATCHERS.each_with_index do |matcher, idx|
-
value = value.gsub(/[_,]/, "") if idx == 0
-
return true, Integer(value) if matcher.match(value)
-
end
-
try_edge_cases?(value)
-
end
-
-
1
def try_edge_cases?(value)
-
return true, Parse::Hexadecimal.value(value) if Parse::Hexadecimal::MATCHER.match(value)
-
return true, Parse::Sexagesimal.value(value) if Parse::Sexagesimal::INTEGER_MATCHER.match(value)
-
return false
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Transform
-
1
class ToNil
-
1
include TransformationMap
-
-
1
set_predefined_values({
-
"" => nil,
-
"~" => nil,
-
"null" => nil
-
})
-
-
1
def transform?(value)
-
return false if value.length > 4
-
return PREDEFINED_VALUES.include?(value), PREDEFINED_VALUES[value]
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Transform
-
1
class ToSymbol
-
1
def transform?(value, options=SafeYAML::OPTIONS)
-
if options[:deserialize_symbols] && value =~ /\A:./
-
if value =~ /\A:(["'])(.*)\1\Z/
-
return true, $2.sub(/^:/, "").to_sym
-
else
-
return true, value.sub(/^:/, "").to_sym
-
end
-
end
-
-
return false
-
end
-
end
-
end
-
end
-
1
module SafeYAML
-
1
class Transform
-
1
module TransformationMap
-
1
def self.included(base)
-
2
base.extend(ClassMethods)
-
end
-
-
1
class CaseAgnosticMap < Hash
-
1
def initialize(*args)
-
2
super
-
end
-
-
1
def include?(key)
-
super(key.downcase)
-
end
-
-
1
def [](key)
-
super(key.downcase)
-
end
-
-
# OK, I actually don't think it's all that important that this map be
-
# frozen.
-
1
def freeze
-
2
self
-
end
-
end
-
-
1
module ClassMethods
-
1
def set_predefined_values(predefined_values)
-
2
if SafeYAML::YAML_ENGINE == "syck"
-
expanded_map = predefined_values.inject({}) do |hash, (key, value)|
-
hash[key] = value
-
hash[key.capitalize] = value
-
hash[key.upcase] = value
-
hash
-
end
-
else
-
2
expanded_map = CaseAgnosticMap.new
-
2
expanded_map.merge!(predefined_values)
-
end
-
-
2
self.const_set(:PREDEFINED_VALUES, expanded_map.freeze)
-
end
-
end
-
end
-
end
-
end
-
1
require 'thread_safe/version'
-
1
require 'thread_safe/synchronized_delegator'
-
-
1
module ThreadSafe
-
1
autoload :Cache, 'thread_safe/cache'
-
1
autoload :Util, 'thread_safe/util'
-
-
# Various classes within allows for +nil+ values to be stored, so a special +NULL+ token is required to indicate the "nil-ness".
-
1
NULL = Object.new
-
-
1
if defined?(JRUBY_VERSION)
-
require 'jruby/synchronized'
-
-
# A thread-safe subclass of Array. This version locks
-
# against the object itself for every method call,
-
# ensuring only one thread can be reading or writing
-
# at a time. This includes iteration methods like
-
# #each.
-
class Array < ::Array
-
include JRuby::Synchronized
-
end
-
-
# A thread-safe subclass of Hash. This version locks
-
# against the object itself for every method call,
-
# ensuring only one thread can be reading or writing
-
# at a time. This includes iteration methods like
-
# #each.
-
class Hash < ::Hash
-
include JRuby::Synchronized
-
end
-
elsif !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
-
# Because MRI never runs code in parallel, the existing
-
# non-thread-safe structures should usually work fine.
-
1
Array = ::Array
-
1
Hash = ::Hash
-
elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
-
require 'monitor'
-
-
class Hash < ::Hash; end
-
class Array < ::Array; end
-
-
[Hash, Array].each do |klass|
-
klass.class_eval do
-
private
-
def _mon_initialize
-
@_monitor = Monitor.new unless @_monitor # avoid double initialisation
-
end
-
-
def self.allocate
-
obj = super
-
obj.send(:_mon_initialize)
-
obj
-
end
-
end
-
-
klass.superclass.instance_methods(false).each do |method|
-
klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
def #{method}(*args)
-
@_monitor.synchronize { super }
-
end
-
RUBY_EVAL
-
end
-
end
-
end
-
end
-
1
require 'thread'
-
-
1
module ThreadSafe
-
1
autoload :JRubyCacheBackend, 'thread_safe/jruby_cache_backend'
-
1
autoload :MriCacheBackend, 'thread_safe/mri_cache_backend'
-
1
autoload :NonConcurrentCacheBackend, 'thread_safe/non_concurrent_cache_backend'
-
1
autoload :AtomicReferenceCacheBackend, 'thread_safe/atomic_reference_cache_backend'
-
1
autoload :SynchronizedCacheBackend, 'thread_safe/synchronized_cache_backend'
-
-
1
ConcurrentCacheBackend = if defined?(RUBY_ENGINE)
-
1
case RUBY_ENGINE
-
when 'jruby'; JRubyCacheBackend
-
1
when 'ruby'; MriCacheBackend
-
when 'rbx'; AtomicReferenceCacheBackend
-
else
-
warn 'ThreadSafe: unsupported Ruby engine, using a fully synchronized ThreadSafe::Cache implementation' if $VERBOSE
-
SynchronizedCacheBackend
-
end
-
else
-
MriCacheBackend
-
end
-
-
1
class Cache < ConcurrentCacheBackend
-
1
KEY_ERROR = defined?(KeyError) ? KeyError : IndexError # there is no KeyError in 1.8 mode
-
-
1
def initialize(options = nil, &block)
-
39
if options.kind_of?(::Hash)
-
validate_options_hash!(options)
-
else
-
39
options = nil
-
end
-
-
39
super(options)
-
39
@default_proc = block
-
end
-
-
1
def [](key)
-
if value = super # non-falsy value is an existing mapping, return it right away
-
value
-
# re-check is done with get_or_default(key, NULL) instead of a simple !key?(key) in order to avoid a race condition, whereby by the time the current thread gets to the key?(key) call
-
# a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrent +nil+ value
-
# would be returned)
-
# note: nil == value check is not technically necessary
-
elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL))
-
@default_proc.call(self, key)
-
else
-
value
-
end
-
end
-
-
1
alias_method :get, :[]
-
1
alias_method :put, :[]=
-
-
1
def fetch(key, default_value = NULL)
-
92
if NULL != (value = get_or_default(key, NULL))
-
10
value
-
82
elsif block_given?
-
82
yield key
-
elsif NULL != default_value
-
default_value
-
else
-
raise_fetch_no_key
-
end
-
end
-
-
1
def fetch_or_store(key, default_value = NULL)
-
fetch(key) do
-
put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value))
-
end
-
end
-
-
def put_if_absent(key, value)
-
computed = false
-
result = compute_if_absent(key) do
-
computed = true
-
value
-
end
-
computed ? nil : result
-
1
end unless method_defined?(:put_if_absent)
-
-
def value?(value)
-
each_value do |v|
-
return true if value.equal?(v)
-
end
-
false
-
1
end unless method_defined?(:value?)
-
-
def keys
-
arr = []
-
each_pair {|k, v| arr << k}
-
arr
-
1
end unless method_defined?(:keys)
-
-
def values
-
arr = []
-
each_pair {|k, v| arr << v}
-
arr
-
1
end unless method_defined?(:values)
-
-
def each_key
-
each_pair {|k, v| yield k}
-
1
end unless method_defined?(:each_key)
-
-
def each_value
-
each_pair {|k, v| yield v}
-
1
end unless method_defined?(:each_value)
-
-
def key(value)
-
each_pair {|k, v| return k if v == value}
-
nil
-
1
end unless method_defined?(:key)
-
1
alias_method :index, :key if RUBY_VERSION < '1.9'
-
-
def empty?
-
each_pair {|k, v| return false}
-
true
-
1
end unless method_defined?(:empty?)
-
-
def size
-
count = 0
-
each_pair {|k, v| count += 1}
-
count
-
1
end unless method_defined?(:size)
-
-
1
def marshal_dump
-
raise TypeError, "can't dump hash with default proc" if @default_proc
-
h = {}
-
each_pair {|k, v| h[k] = v}
-
h
-
end
-
-
1
def marshal_load(hash)
-
initialize
-
populate_from(hash)
-
end
-
-
1
undef :freeze
-
-
1
private
-
1
def raise_fetch_no_key
-
raise KEY_ERROR, 'key not found'
-
end
-
-
1
def initialize_copy(other)
-
super
-
populate_from(other)
-
end
-
-
1
def populate_from(hash)
-
hash.each_pair {|k, v| self[k] = v}
-
self
-
end
-
-
1
def validate_options_hash!(options)
-
if (initial_capacity = options[:initial_capacity]) && (!initial_capacity.kind_of?(Fixnum) || initial_capacity < 0)
-
raise ArgumentError, ":initial_capacity must be a positive Fixnum"
-
end
-
if (load_factor = options[:load_factor]) && (!load_factor.kind_of?(Numeric) || load_factor <= 0 || load_factor > 1)
-
raise ArgumentError, ":load_factor must be a number between 0 and 1"
-
end
-
end
-
end
-
end
-
1
module ThreadSafe
-
1
class MriCacheBackend < NonConcurrentCacheBackend
-
# We can get away with a single global write lock (instead of a per-instance one) because of the GVL/green threads.
-
#
-
# The previous implementation used `Thread.critical` on 1.8 MRI to implement the 4 composed atomic operations (`put_if_absent`, `replace_pair`,
-
# `replace_if_exists`, `delete_pair`) this however doesn't work for `compute_if_absent` because on 1.8 the Mutex class is itself implemented
-
# via `Thread.critical` and a call to `Mutex#lock` does not restore the previous `Thread.critical` value (thus any synchronisation clears the
-
# `Thread.critical` flag and we loose control). This poses a problem as the provided block might use synchronisation on its own.
-
#
-
# NOTE: a neat idea of writing a c-ext to manually perform atomic put_if_absent, while relying on Ruby not releasing a GVL while calling
-
# a c-ext will not work because of the potentially Ruby implemented `#hash` and `#eql?` key methods.
-
1
WRITE_LOCK = Mutex.new
-
-
1
def []=(key, value)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def compute_if_absent(key)
-
90
if stored_value = _get(key) # fast non-blocking path for the most likely case
-
stored_value
-
else
-
180
WRITE_LOCK.synchronize { super }
-
end
-
end
-
-
1
def compute_if_present(key)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def compute(key)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def merge_pair(key, value)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def replace_pair(key, old_value, new_value)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def replace_if_exists(key, new_value)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def get_and_set(key, value)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def delete(key)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def delete_pair(key, value)
-
WRITE_LOCK.synchronize { super }
-
end
-
-
1
def clear
-
WRITE_LOCK.synchronize { super }
-
end
-
end
-
end
-
1
module ThreadSafe
-
1
class NonConcurrentCacheBackend
-
# WARNING: all public methods of the class must operate on the @backend directly without calling each other. This is important
-
# because of the SynchronizedCacheBackend which uses a non-reentrant mutex for perfomance reasons.
-
1
def initialize(options = nil)
-
39
@backend = {}
-
end
-
-
1
def [](key)
-
90
@backend[key]
-
end
-
-
1
def []=(key, value)
-
@backend[key] = value
-
end
-
-
1
def compute_if_absent(key)
-
90
if NULL != (stored_value = @backend.fetch(key, NULL))
-
stored_value
-
else
-
90
@backend[key] = yield
-
end
-
end
-
-
1
def replace_pair(key, old_value, new_value)
-
if pair?(key, old_value)
-
@backend[key] = new_value
-
true
-
else
-
false
-
end
-
end
-
-
1
def replace_if_exists(key, new_value)
-
if NULL != (stored_value = @backend.fetch(key, NULL))
-
@backend[key] = new_value
-
stored_value
-
end
-
end
-
-
1
def compute_if_present(key)
-
if NULL != (stored_value = @backend.fetch(key, NULL))
-
store_computed_value(key, yield(stored_value))
-
end
-
end
-
-
1
def compute(key)
-
store_computed_value(key, yield(@backend[key]))
-
end
-
-
1
def merge_pair(key, value)
-
if NULL == (stored_value = @backend.fetch(key, NULL))
-
@backend[key] = value
-
else
-
store_computed_value(key, yield(stored_value))
-
end
-
end
-
-
1
def get_and_set(key, value)
-
stored_value = @backend[key]
-
@backend[key] = value
-
stored_value
-
end
-
-
1
def key?(key)
-
@backend.key?(key)
-
end
-
-
1
def value?(value)
-
@backend.value?(value)
-
end
-
-
1
def delete(key)
-
@backend.delete(key)
-
end
-
-
1
def delete_pair(key, value)
-
if pair?(key, value)
-
@backend.delete(key)
-
true
-
else
-
false
-
end
-
end
-
-
1
def clear
-
@backend.clear
-
self
-
end
-
-
1
def each_pair
-
dupped_backend.each_pair do |k, v|
-
yield k, v
-
end
-
self
-
end
-
-
1
def size
-
@backend.size
-
end
-
-
1
def get_or_default(key, default_value)
-
92
@backend.fetch(key, default_value)
-
end
-
-
1
alias_method :_get, :[]
-
1
alias_method :_set, :[]=
-
1
private :_get, :_set
-
1
private
-
1
def initialize_copy(other)
-
super
-
@backend = {}
-
self
-
end
-
-
1
def dupped_backend
-
@backend.dup
-
end
-
-
1
def pair?(key, expected_value)
-
NULL != (stored_value = @backend.fetch(key, NULL)) && expected_value.equal?(stored_value)
-
end
-
-
1
def store_computed_value(key, new_value)
-
if new_value.nil?
-
@backend.delete(key)
-
nil
-
else
-
@backend[key] = new_value
-
end
-
end
-
end
-
end
-
1
require 'delegate'
-
1
require 'monitor'
-
-
# This class provides a trivial way to synchronize all calls to a given object
-
# by wrapping it with a `Delegator` that performs `Monitor#enter/exit` calls
-
# around the delegated `#send`. Example:
-
#
-
# array = [] # not thread-safe on many impls
-
# array = SynchronizedDelegator.new([]) # thread-safe
-
#
-
# A simple `Monitor` provides a very coarse-grained way to synchronize a given
-
# object, in that it will cause synchronization for methods that have no
-
# need for it, but this is a trivial way to get thread-safety where none may
-
# exist currently on some implementations.
-
#
-
# This class is currently being considered for inclusion into stdlib, via
-
# https://bugs.ruby-lang.org/issues/8556
-
class SynchronizedDelegator < SimpleDelegator
-
1
def setup
-
@old_abort = Thread.abort_on_exception
-
Thread.abort_on_exception = true
-
end
-
-
1
def teardown
-
Thread.abort_on_exception = @old_abort
-
end
-
-
1
def initialize(obj)
-
__setobj__(obj)
-
@monitor = Monitor.new
-
end
-
-
1
def method_missing(method, *args, &block)
-
monitor = @monitor
-
begin
-
monitor.enter
-
super
-
ensure
-
monitor.exit
-
end
-
end
-
-
# Work-around for 1.8 std-lib not passing block around to delegate.
-
# @private
-
def method_missing(method, *args, &block)
-
monitor = @monitor
-
begin
-
monitor.enter
-
target = self.__getobj__
-
if target.respond_to?(method)
-
target.__send__(method, *args, &block)
-
else
-
super(method, *args, &block)
-
end
-
ensure
-
monitor.exit
-
end
-
1
end if RUBY_VERSION[0, 3] == '1.8'
-
-
1
end unless defined?(SynchronizedDelegator)
-
1
module ThreadSafe
-
1
VERSION = "0.3.4"
-
end
-
-
# NOTE: <= 0.2.0 used Threadsafe::VERSION
-
# @private
-
1
module Threadsafe
-
-
# @private
-
1
def self.const_missing(name)
-
name = name.to_sym
-
if ThreadSafe.const_defined?(name)
-
warn "[DEPRECATION] `Threadsafe::#{name}' is deprecated, use `ThreadSafe::#{name}' instead."
-
ThreadSafe.const_get(name)
-
else
-
warn "[DEPRECATION] the `Threadsafe' module is deprecated, please use `ThreadSafe` instead."
-
super
-
end
-
end
-
-
end
-
1
require File.join(File.dirname(__FILE__), "timecop", "timecop")
-
1
require File.join(File.dirname(__FILE__), "timecop", "version")
-
-
1
class Time #:nodoc:
-
1
class << self
-
1
def mock_time
-
64
mocked_time_stack_item = Timecop.top_stack_item
-
64
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.time(self)
-
end
-
-
1
alias_method :now_without_mock_time, :now
-
-
1
def now_with_mock_time
-
64
mock_time || now_without_mock_time
-
end
-
-
1
alias_method :now, :now_with_mock_time
-
-
1
alias_method :new_without_mock_time, :new
-
-
1
def new_with_mock_time(*args)
-
begin
-
raise ArgumentError.new if args.size <= 0
-
new_without_mock_time(*args)
-
rescue ArgumentError
-
now
-
end
-
end
-
-
1
alias_method :new, :new_with_mock_time
-
end
-
end
-
-
1
if Object.const_defined?(:Date) && Date.respond_to?(:today)
-
1
class Date #:nodoc:
-
1
class << self
-
1
def mock_date
-
mocked_time_stack_item = Timecop.top_stack_item
-
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.date(self)
-
end
-
-
1
alias_method :today_without_mock_date, :today
-
-
1
def today_with_mock_date
-
mock_date || today_without_mock_date
-
end
-
-
1
alias_method :today, :today_with_mock_date
-
end
-
end
-
end
-
-
1
if Object.const_defined?(:DateTime) && DateTime.respond_to?(:now)
-
1
class DateTime #:nodoc:
-
1
class << self
-
1
def mock_time
-
mocked_time_stack_item = Timecop.top_stack_item
-
mocked_time_stack_item.nil? ? nil : mocked_time_stack_item.datetime(self)
-
end
-
-
1
def now_without_mock_time
-
Time.now_without_mock_time.to_datetime
-
end
-
-
1
def now_with_mock_time
-
mock_time || now_without_mock_time
-
end
-
-
1
alias_method :now, :now_with_mock_time
-
end
-
end
-
-
# for ruby1.8
-
1
unless Time::public_instance_methods.include? :to_datetime
-
class DateTime
-
class << self
-
def now_without_mock_time
-
Time.now_without_mock_time.send(:to_datetime)
-
end
-
end
-
end
-
end
-
end
-
1
class Timecop
-
# A data class for carrying around "time movement" objects. Makes it easy to keep track of the time
-
# movements on a simple stack.
-
1
class TimeStackItem #:nodoc:
-
1
attr_reader :mock_type
-
-
1
def initialize(mock_type, *args)
-
raise "Unknown mock_type #{mock_type}" unless [:freeze, :travel, :scale].include?(mock_type)
-
@scaling_factor = args.shift if mock_type == :scale
-
@mock_type = mock_type
-
@time = parse_time(*args)
-
@time_was = Time.now_without_mock_time
-
@travel_offset = compute_travel_offset
-
@dst_adjustment = compute_dst_adjustment(@time)
-
end
-
-
1
def year
-
time.year
-
end
-
-
1
def month
-
time.month
-
end
-
-
1
def day
-
time.day
-
end
-
-
1
def hour
-
time.hour
-
end
-
-
1
def min
-
time.min
-
end
-
-
1
def sec
-
time.sec
-
end
-
-
1
def utc_offset
-
time.utc_offset
-
end
-
-
1
def travel_offset
-
@travel_offset
-
end
-
-
1
def scaling_factor
-
@scaling_factor
-
end
-
-
1
def time(klass = time_klass) #:nodoc:
-
begin
-
actual_time = klass.at(@time)
-
calculated_time = klass.at(@time.to_f)
-
time = times_are_equal_within_epsilon(actual_time, calculated_time, 1) ? actual_time : calculated_time
-
rescue
-
time = klass.at(@time.to_f)
-
end
-
-
if travel_offset.nil?
-
time
-
elsif scaling_factor.nil?
-
klass.at(Time.now_without_mock_time + travel_offset)
-
else
-
klass.at(scaled_time)
-
end
-
end
-
-
1
def scaled_time
-
(@time + (Time.now_without_mock_time - @time_was) * scaling_factor).to_f
-
end
-
-
1
def date(date_klass = Date)
-
date_klass.jd(time.__send__(:to_date).jd)
-
end
-
-
1
def datetime(datetime_klass = DateTime)
-
our_offset = utc_offset + dst_adjustment
-
-
if Float.method_defined?(:to_r)
-
fractions_of_a_second = time.to_f % 1
-
datetime_klass.new(year, month, day, hour, min, sec + fractions_of_a_second, utc_offset_to_rational(our_offset))
-
else
-
our_offset = utc_offset + dst_adjustment
-
datetime_klass.new(year, month, day, hour, min, sec, utc_offset_to_rational(our_offset))
-
end
-
end
-
-
1
def dst_adjustment
-
@dst_adjustment
-
end
-
-
1
private
-
-
1
def rational_to_utc_offset(rational)
-
((24.0 / rational.denominator) * rational.numerator) * (60 * 60)
-
end
-
-
1
def utc_offset_to_rational(utc_offset)
-
Rational(utc_offset, 24 * 60 * 60)
-
end
-
-
1
def parse_time(*args)
-
arg = args.shift
-
if arg.is_a?(Time)
-
if Timecop.active_support != false && arg.respond_to?(:in_time_zone)
-
arg.in_time_zone
-
else
-
arg.getlocal
-
end
-
elsif Object.const_defined?(:DateTime) && arg.is_a?(DateTime)
-
expected_time = time_klass.local(arg.year, arg.month, arg.day, arg.hour, arg.min, arg.sec)
-
expected_time += expected_time.utc_offset - rational_to_utc_offset(arg.offset)
-
expected_time + compute_dst_adjustment(expected_time)
-
elsif Object.const_defined?(:Date) && arg.is_a?(Date)
-
time_klass.local(arg.year, arg.month, arg.day, 0, 0, 0)
-
elsif args.empty? && arg.kind_of?(Integer)
-
Time.now + arg
-
elsif arg.nil?
-
Time.now
-
else
-
if arg.is_a?(String) && Timecop.active_support != false && Time.respond_to?(:parse)
-
Time.parse(arg)
-
else
-
# we'll just assume it's a list of y/m/d/h/m/s
-
year = arg || 2000
-
month = args.shift || 1
-
day = args.shift || 1
-
hour = args.shift || 0
-
minute = args.shift || 0
-
second = args.shift || 0
-
time_klass.local(year, month, day, hour, minute, second)
-
end
-
end
-
end
-
-
1
def compute_dst_adjustment(time)
-
return 0 if !(time.dst? ^ Time.now.dst?)
-
return -1 * 60 * 60 if time.dst?
-
return 60 * 60
-
end
-
-
1
def compute_travel_offset
-
return nil if mock_type == :freeze
-
time - Time.now_without_mock_time
-
end
-
-
1
def times_are_equal_within_epsilon t1, t2, epsilon_in_seconds
-
(t1 - t2).abs < epsilon_in_seconds
-
end
-
-
1
def time_klass
-
Time.respond_to?(:zone) && Time.zone ? Time.zone : Time
-
end
-
end
-
end
-
1
require 'singleton'
-
1
require File.join(File.dirname(__FILE__), "time_extensions")
-
1
require File.join(File.dirname(__FILE__), "time_stack_item")
-
-
# Timecop
-
# * Wrapper class for manipulating the extensions to the Time, Date, and DateTime objects
-
# * Allows us to "freeze" time in our Ruby applications.
-
# * Optionally allows time travel to simulate a running clock, such time is not technically frozen.
-
#
-
# This is very useful when your app's functionality is dependent on time (e.g.
-
# anything that might expire). This will allow us to alter the return value of
-
# Date.today, Time.now, and DateTime.now, such that our application code _never_ has to change.
-
1
class Timecop
-
1
include Singleton
-
-
1
class << self
-
1
attr_accessor :active_support
-
-
# Allows you to run a block of code and "fake" a time throughout the execution of that block.
-
# This is particularly useful for writing test methods where the passage of time is critical to the business
-
# logic being tested. For example:
-
#
-
# joe = User.find(1)
-
# joe.purchase_home()
-
# assert !joe.mortgage_due?
-
# Timecop.freeze(2008, 10, 5) do
-
# assert joe.mortgage_due?
-
# end
-
#
-
# freeze and travel will respond to several different arguments:
-
# 1. Timecop.freeze(time_inst)
-
# 2. Timecop.freeze(datetime_inst)
-
# 3. Timecop.freeze(date_inst)
-
# 4. Timecop.freeze(offset_in_seconds)
-
# 5. Timecop.freeze(year, month, day, hour=0, minute=0, second=0)
-
#
-
# When a block is also passed, Time.now, DateTime.now and Date.today are all reset to their
-
# previous values after the block has finished executing. This allows us to nest multiple
-
# calls to Timecop.travel and have each block maintain it's concept of "now."
-
#
-
# * Note: Timecop.freeze will actually freeze time. This can cause unanticipated problems if
-
# benchmark or other timing calls are executed, which implicitly expect Time to actually move
-
# forward.
-
#
-
# * Rails Users: Be especially careful when setting this in your development environment in a
-
# rails project. Generators will load your environment, including the migration generator,
-
# which will lead to files being generated with the timestamp set by the Timecop.freeze call
-
# in your dev environment
-
#
-
# Returns the value of the block if one is given, or the mocked time.
-
1
def freeze(*args, &block)
-
send_travel(:freeze, *args, &block)
-
end
-
-
# Allows you to run a block of code and "fake" a time throughout the execution of that block.
-
# See Timecop#freeze for a sample of how to use (same exact usage syntax)
-
#
-
# * Note: Timecop.travel will not freeze time (as opposed to Timecop.freeze). This is a particularly
-
# good candidate for use in environment files in rails projects.
-
#
-
# Returns the value of the block if one is given, or the mocked time.
-
1
def travel(*args, &block)
-
send_travel(:travel, *args, &block)
-
end
-
-
# Allows you to run a block of code and "scale" a time throughout the execution of that block.
-
# The first argument is a scaling factor, for example:
-
# Timecop.scale(2) do
-
# ... time will 'go' twice as fast here
-
# end
-
# See Timecop#freeze for exact usage of the other arguments
-
#
-
# Returns the value of the block if one is given, or the mocked time.
-
1
def scale(*args, &block)
-
send_travel(:scale, *args, &block)
-
end
-
-
1
def baseline
-
instance.send(:baseline)
-
end
-
-
1
def baseline=(baseline)
-
instance.send(:baseline=, baseline)
-
end
-
-
# Reverts back to system's Time.now, Date.today and DateTime.now (if it exists) permamently when
-
# no block argument is given, or temporarily reverts back to the system's time temporarily for
-
# the given block.
-
1
def return(&block)
-
if block_given?
-
instance.send(:return, &block)
-
else
-
instance.send(:unmock!)
-
nil
-
end
-
end
-
-
1
def return_to_baseline
-
instance.send(:return_to_baseline)
-
Time.now
-
end
-
-
1
def top_stack_item #:nodoc:
-
64
instance.instance_variable_get(:@_stack).last
-
end
-
-
1
private
-
1
def send_travel(mock_type, *args, &block)
-
val = instance.send(:travel, mock_type, *args, &block)
-
block_given? ? val : Time.now
-
end
-
end
-
-
1
private
-
-
1
def baseline=(baseline)
-
@baseline = baseline
-
@_stack << TimeStackItem.new(:travel, baseline)
-
end
-
-
1
def initialize #:nodoc:
-
1
@_stack = []
-
end
-
-
1
def travel(mock_type, *args, &block) #:nodoc:
-
stack_item = TimeStackItem.new(mock_type, *args)
-
-
@_stack << stack_item
-
-
if block_given?
-
begin
-
yield stack_item.time
-
ensure
-
@_stack.pop
-
end
-
end
-
end
-
-
1
def return(&block)
-
current_stack = @_stack
-
current_baseline = @baseline
-
unmock!
-
yield
-
@_stack = current_stack
-
@baseline = current_baseline
-
end
-
-
1
def unmock! #:nodoc:
-
@baseline = nil
-
@_stack = []
-
end
-
-
1
def return_to_baseline
-
if @baseline
-
@_stack = [@_stack.shift]
-
else
-
unmock!
-
end
-
end
-
end
-
1
class Timecop
-
1
VERSION = "0.6.1"
-
end
-
1
require 'singleton'
-
-
1
require 'addressable/uri'
-
1
require 'addressable/template'
-
1
require 'crack'
-
-
1
require 'webmock/deprecation'
-
1
require 'webmock/version'
-
-
1
require 'webmock/errors'
-
-
1
require 'webmock/util/query_mapper'
-
1
require 'webmock/util/uri'
-
1
require 'webmock/util/headers'
-
1
require 'webmock/util/hash_counter'
-
1
require 'webmock/util/hash_keys_stringifier'
-
1
require 'webmock/util/json'
-
1
require 'webmock/util/version_checker'
-
-
1
require 'webmock/matchers/hash_including_matcher'
-
-
1
require 'webmock/request_pattern'
-
1
require 'webmock/request_signature'
-
1
require 'webmock/responses_sequence'
-
1
require 'webmock/request_stub'
-
1
require 'webmock/response'
-
1
require 'webmock/rack_response'
-
-
1
require 'webmock/stub_request_snippet'
-
-
1
require 'webmock/assertion_failure'
-
1
require 'webmock/request_execution_verifier'
-
1
require 'webmock/config'
-
1
require 'webmock/callback_registry'
-
1
require 'webmock/request_registry'
-
1
require 'webmock/stub_registry'
-
1
require 'webmock/api'
-
-
1
require 'webmock/http_lib_adapters/http_lib_adapter_registry'
-
1
require 'webmock/http_lib_adapters/http_lib_adapter'
-
1
require 'webmock/http_lib_adapters/net_http'
-
1
require 'webmock/http_lib_adapters/http_gem_adapter'
-
1
require 'webmock/http_lib_adapters/httpclient_adapter'
-
1
require 'webmock/http_lib_adapters/patron_adapter'
-
1
require 'webmock/http_lib_adapters/curb_adapter'
-
1
require 'webmock/http_lib_adapters/em_http_request_adapter'
-
1
require 'webmock/http_lib_adapters/typhoeus_hydra_adapter'
-
1
require 'webmock/http_lib_adapters/excon_adapter'
-
-
1
require 'webmock/webmock'
-
-
-
1
module WebMock
-
1
module API
-
1
extend self
-
-
1
def stub_request(method, uri)
-
WebMock::StubRegistry.instance.
-
15
register_request_stub(WebMock::RequestStub.new(method, uri))
-
end
-
-
1
alias_method :stub_http_request, :stub_request
-
-
1
def a_request(method, uri)
-
3
WebMock::RequestPattern.new(method, uri)
-
end
-
-
1
class << self
-
1
alias :request :a_request
-
end
-
-
1
def assert_requested(*args, &block)
-
if not args[0].is_a?(WebMock::RequestStub)
-
args = convert_uri_method_and_options_to_request_and_options(*args, &block)
-
elsif block
-
raise ArgumentError, "assert_requested with a stub object, doesn't accept blocks"
-
end
-
assert_request_requested(*args)
-
end
-
-
1
def assert_not_requested(*args, &block)
-
if not args[0].is_a?(WebMock::RequestStub)
-
args = convert_uri_method_and_options_to_request_and_options(*args, &block)
-
elsif block
-
raise ArgumentError, "assert_not_requested with a stub object, doesn't accept blocks"
-
end
-
assert_request_not_requested(*args)
-
end
-
-
# Similar to RSpec::Mocks::ArgumentMatchers#hash_including()
-
#
-
# Matches a hash that includes the specified key(s) or key/value pairs.
-
# Ignores any additional keys.
-
#
-
# @example
-
#
-
# object.should_receive(:message).with(hash_including(:key => val))
-
# object.should_receive(:message).with(hash_including(:key))
-
# object.should_receive(:message).with(hash_including(:key, :key2 => val2))
-
1
def hash_including(*args)
-
if defined?(super)
-
super
-
else
-
WebMock::Matchers::HashIncludingMatcher.new(anythingize_lonely_keys(*args))
-
end
-
end
-
-
1
def remove_request_stub(stub)
-
WebMock::StubRegistry.instance.remove_request_stub(stub)
-
end
-
-
1
private
-
-
1
def convert_uri_method_and_options_to_request_and_options(*args, &block)
-
request = WebMock::RequestPattern.new(*args).with(&block)
-
[request, args[2] || {}]
-
end
-
-
1
def assert_request_requested(request, options = {})
-
verifier = WebMock::RequestExecutionVerifier.new(request, options.delete(:times) || 1)
-
WebMock::AssertionFailure.failure(verifier.failure_message) unless verifier.matches?
-
end
-
-
1
def assert_request_not_requested(request, options = {})
-
verifier = WebMock::RequestExecutionVerifier.new(request, options.delete(:times))
-
WebMock::AssertionFailure.failure(verifier.failure_message_when_negated) unless verifier.does_not_match?
-
end
-
-
#this is a based on RSpec::Mocks::ArgumentMatchers#anythingize_lonely_keys
-
1
def anythingize_lonely_keys(*args)
-
hash = args.last.class == Hash ? args.delete_at(-1) : {}
-
args.each { | arg | hash[arg] = WebMock::Matchers::AnyArgMatcher.new(nil) }
-
hash
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class AssertionFailure
-
1
@error_class = RuntimeError
-
1
class << self
-
1
attr_accessor :error_class
-
1
def failure(message)
-
raise @error_class.new(message)
-
end
-
end
-
end
-
end
-
1
module WebMock
-
1
class CallbackRegistry
-
1
@@callbacks = []
-
-
1
def self.add_callback(options, block)
-
@@callbacks << {:options => options, :block => block}
-
end
-
-
1
def self.callbacks
-
@@callbacks
-
end
-
-
1
def self.invoke_callbacks(options, request_signature, response)
-
15
return if @@callbacks.empty?
-
CallbackRegistry.callbacks.each do |callback|
-
except = callback[:options][:except]
-
real_only = callback[:options][:real_requests_only]
-
unless except && except.include?(options[:lib])
-
if !real_only || options[:real_request]
-
callback[:block].call(request_signature, response)
-
end
-
end
-
end
-
end
-
-
1
def self.reset
-
@@callbacks = []
-
end
-
-
1
def self.any_callbacks?
-
!@@callbacks.empty?
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class Config
-
1
include Singleton
-
-
1
def initialize
-
1
@show_stubbing_instructions = true
-
end
-
-
1
attr_accessor :allow_net_connect
-
1
attr_accessor :allow_localhost
-
1
attr_accessor :allow
-
1
attr_accessor :net_http_connect_on_start
-
1
attr_accessor :show_stubbing_instructions
-
1
attr_accessor :query_values_notation
-
end
-
end
-
1
module WebMock
-
1
class Deprecation
-
1
class << self
-
1
def warning(message)
-
warn "WebMock deprecation warning: #{message}"
-
end
-
end
-
end
-
end
-
1
module WebMock
-
-
1
class NetConnectNotAllowedError < Exception
-
1
def initialize(request_signature)
-
text = [
-
"Real HTTP connections are disabled. Unregistered request: #{request_signature}",
-
stubbing_instructions(request_signature),
-
request_stubs,
-
"="*60
-
].compact.join("\n\n")
-
super(text)
-
end
-
-
1
private
-
-
1
def request_stubs
-
return if WebMock::StubRegistry.instance.request_stubs.empty?
-
text = "registered request stubs:\n"
-
WebMock::StubRegistry.instance.request_stubs.each do |stub|
-
text << "\n#{WebMock::StubRequestSnippet.new(stub).to_s(false)}"
-
end
-
text
-
end
-
-
1
def stubbing_instructions(request_signature)
-
return unless WebMock.show_stubbing_instructions?
-
text = ""
-
request_stub = RequestStub.from_request_signature(request_signature)
-
text << "You can stub this request with the following snippet:\n\n"
-
text << WebMock::StubRequestSnippet.new(request_stub).to_s
-
text
-
end
-
end
-
-
end
-
1
begin
-
1
require 'curb'
-
rescue LoadError
-
# curb not found
-
end
-
-
1
if defined?(Curl)
-
WebMock::VersionChecker.new('Curb', Curl::CURB_VERSION, '0.7.16').check_version!
-
-
module WebMock
-
module HttpLibAdapters
-
class CurbAdapter < HttpLibAdapter
-
adapter_for :curb
-
-
OriginalCurlEasy = Curl::Easy unless const_defined?(:OriginalCurlEasy)
-
-
def self.enable!
-
Curl.send(:remove_const, :Easy)
-
Curl.send(:const_set, :Easy, Curl::WebMockCurlEasy)
-
end
-
-
def self.disable!
-
Curl.send(:remove_const, :Easy)
-
Curl.send(:const_set, :Easy, OriginalCurlEasy)
-
end
-
-
# Borrowed from Patron:
-
# http://github.com/toland/patron/blob/master/lib/patron/response.rb
-
def self.parse_header_string(header_string)
-
status, headers = nil, {}
-
-
header_string.split(/\r\n/).each do |header|
-
if header =~ %r|^HTTP/1.[01] \d\d\d (.*)|
-
status = $1
-
else
-
parts = header.split(':', 2)
-
unless parts.empty?
-
parts[1].strip! unless parts[1].nil?
-
if headers.has_key?(parts[0])
-
headers[parts[0]] = [headers[parts[0]]] unless headers[parts[0]].kind_of? Array
-
headers[parts[0]] << parts[1]
-
else
-
headers[parts[0]] = parts[1]
-
end
-
end
-
end
-
end
-
-
return status, headers
-
end
-
end
-
end
-
end
-
-
module Curl
-
class WebMockCurlEasy < Curl::Easy
-
def curb_or_webmock
-
-
request_signature = build_request_signature
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
build_curb_response(webmock_response)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :curb}, request_signature, webmock_response)
-
invoke_curb_callbacks
-
true
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
res = yield
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = build_webmock_response
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :curb, :real_request => true}, request_signature,
-
webmock_response)
-
end
-
res
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
def build_request_signature
-
method = @webmock_method.to_s.downcase.to_sym
-
-
uri = WebMock::Util::URI.heuristic_parse(self.url)
-
uri.path = uri.normalized_path.gsub("[^:]//","/")
-
uri.user = self.username
-
uri.password = self.password
-
-
request_body = case method
-
when :post
-
self.post_body || @post_body
-
when :put
-
@put_data
-
else
-
nil
-
end
-
-
request_signature = WebMock::RequestSignature.new(
-
method,
-
uri.to_s,
-
:body => request_body,
-
:headers => self.headers
-
)
-
request_signature
-
end
-
-
def build_curb_response(webmock_response)
-
raise Curl::Err::TimeoutError if webmock_response.should_timeout
-
webmock_response.raise_error_if_any
-
-
@body_str = webmock_response.body
-
@response_code = webmock_response.status[0]
-
-
@header_str = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}\r\n"
-
if webmock_response.headers
-
@header_str << webmock_response.headers.map do |k,v|
-
"#{k}: #{v.is_a?(Array) ? v.join(", ") : v}"
-
end.join("\r\n")
-
-
location = webmock_response.headers['Location']
-
if self.follow_location? && location
-
@last_effective_url = location
-
webmock_follow_location(location)
-
end
-
-
@content_type = webmock_response.headers["Content-Type"]
-
@transfer_encoding = webmock_response.headers["Transfer-Encoding"]
-
end
-
-
@last_effective_url ||= self.url
-
end
-
-
def webmock_follow_location(location)
-
first_url = self.url
-
self.url = location
-
-
curb_or_webmock do
-
send( "http_#{@webmock_method}_without_webmock" )
-
end
-
-
self.url = first_url
-
end
-
-
def invoke_curb_callbacks
-
@on_progress.call(0.0,1.0,0.0,1.0) if @on_progress
-
self.header_str.lines.each { |header_line| @on_header.call header_line } if @on_header
-
if @on_body
-
if chunked_response?
-
self.body_str.each do |chunk|
-
@on_body.call(chunk)
-
end
-
else
-
@on_body.call(self.body_str)
-
end
-
end
-
@on_complete.call(self) if @on_complete
-
-
case response_code
-
when 200..299
-
@on_success.call(self) if @on_success
-
when 400..499
-
@on_missing.call(self, self.response_code) if @on_missing
-
when 500..599
-
@on_failure.call(self, self.response_code) if @on_failure
-
end
-
end
-
-
def chunked_response?
-
@transfer_encoding == 'chunked' && self.body_str.respond_to?(:each)
-
end
-
-
def build_webmock_response
-
status, headers =
-
WebMock::HttpLibAdapters::CurbAdapter.parse_header_string(self.header_str)
-
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [self.response_code, status]
-
webmock_response.body = self.body_str
-
webmock_response.headers = headers
-
webmock_response
-
end
-
-
###
-
### Mocks of Curl::Easy methods below here.
-
###
-
-
def http(method)
-
@webmock_method = method
-
super
-
end
-
-
%w[ get head delete ].each do |verb|
-
define_method "http_#{verb}" do
-
@webmock_method = verb
-
super()
-
end
-
end
-
-
def http_put data = nil
-
@webmock_method = :put
-
@put_data = data if data
-
super
-
end
-
alias put http_put
-
-
def http_post *data
-
@webmock_method = :post
-
@post_body = data.join('&') if data && !data.empty?
-
super
-
end
-
alias post http_post
-
-
def perform
-
@webmock_method ||= :get
-
curb_or_webmock { super }
-
end
-
-
def put_data= data
-
@webmock_method = :put
-
@put_data = data
-
super
-
end
-
-
def post_body= data
-
@webmock_method = :post
-
super
-
end
-
-
def delete= value
-
@webmock_method = :delete if value
-
super
-
end
-
-
def head= value
-
@webmock_method = :head if value
-
super
-
end
-
-
def body_str
-
@body_str || super
-
end
-
alias body body_str
-
-
def response_code
-
@response_code || super
-
end
-
-
def header_str
-
@header_str || super
-
end
-
alias head header_str
-
-
def last_effective_url
-
@last_effective_url || super
-
end
-
-
def content_type
-
@content_type || super
-
end
-
-
%w[ success failure missing header body complete progress ].each do |callback|
-
class_eval <<-METHOD, __FILE__, __LINE__
-
def on_#{callback} &block
-
@on_#{callback} = block
-
super
-
end
-
METHOD
-
end
-
end
-
end
-
end
-
1
if defined?(EventMachine::HttpRequest)
-
module WebMock
-
module HttpLibAdapters
-
class EmHttpRequestAdapter < HttpLibAdapter
-
adapter_for :em_http_request
-
-
OriginalHttpRequest = EventMachine::HttpRequest unless const_defined?(:OriginalHttpRequest)
-
-
def self.enable!
-
EventMachine.send(:remove_const, :HttpRequest)
-
EventMachine.send(:const_set, :HttpRequest, EventMachine::WebMockHttpRequest)
-
end
-
-
def self.disable!
-
EventMachine.send(:remove_const, :HttpRequest)
-
EventMachine.send(:const_set, :HttpRequest, OriginalHttpRequest)
-
end
-
end
-
end
-
end
-
-
-
module EventMachine
-
class WebMockHttpRequest < EventMachine::HttpRequest
-
-
include HttpEncoding
-
-
class WebMockHttpClient < EventMachine::HttpClient
-
-
def setup(response, uri, error = nil)
-
@last_effective_url = @uri = uri
-
if error
-
on_error(error)
-
fail(self)
-
else
-
EM.next_tick do
-
receive_data(response)
-
succeed(self)
-
end
-
end
-
end
-
-
def unbind
-
end
-
-
def close_connection
-
end
-
end
-
-
def send_request(&block)
-
request_signature = build_request_signature
-
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :em_http_request}, request_signature, webmock_response)
-
client = WebMockHttpClient.new(nil)
-
client.on_error("WebMock timeout error") if webmock_response.should_timeout
-
client.setup(make_raw_response(webmock_response), @uri,
-
webmock_response.should_timeout ? "WebMock timeout error" : nil)
-
client
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
http = super
-
http.callback {
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = build_webmock_response(http)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :em_http_request, :real_request => true}, request_signature,
-
webmock_response)
-
end
-
}
-
http
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
private
-
-
def build_webmock_response(http)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [http.response_header.status, http.response_header.http_reason]
-
webmock_response.headers = http.response_header
-
webmock_response.body = http.response
-
webmock_response
-
end
-
-
def build_request_signature
-
if @req
-
options = @req.options
-
method = @req.method
-
uri = @req.uri.dup
-
else
-
options = @options
-
method = @method
-
uri = @uri.dup
-
end
-
-
if options[:authorization] || options['authorization']
-
auth = (options[:authorization] || options['authorization'])
-
userinfo = auth.join(':')
-
userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo)
-
options.reject! {|k,v| k.to_s == 'authorization' } #we added it to url userinfo
-
uri.userinfo = userinfo
-
end
-
-
uri.query = encode_query(@req.uri, options[:query]).slice(/\?(.*)/, 1)
-
-
body = options[:body] || options['body']
-
body = form_encode_body(body) if body.is_a?(Hash)
-
-
WebMock::RequestSignature.new(
-
method.downcase.to_sym,
-
uri.to_s,
-
:body => body,
-
:headers => (options[:head] || options['head'])
-
)
-
end
-
-
-
def make_raw_response(response)
-
response.raise_error_if_any
-
-
status, headers, body = response.status, response.headers, response.body
-
-
response_string = []
-
response_string << "HTTP/1.1 #{status[0]} #{status[1]}"
-
-
headers.each do |header, value|
-
value = value.join(", ") if value.is_a?(Array)
-
-
# WebMock's internal processing will not handle the body
-
# correctly if the header indicates that it is chunked, unless
-
# we also create all the chunks.
-
# It's far easier just to remove the header.
-
next if header =~ /transfer-encoding/i && value =~/chunked/i
-
-
response_string << "#{header}: #{value}"
-
end if headers
-
-
response_string << "" << body
-
response_string.join("\n")
-
end
-
end
-
end
-
end
-
1
begin
-
1
require 'em-http-request'
-
rescue LoadError
-
# em-http-request not found
-
end
-
-
1
if defined?(EventMachine::HttpConnection)
-
require File.expand_path(File.dirname(__FILE__) + '/em_http_request/em_http_request_1_x')
-
else
-
1
require File.expand_path(File.dirname(__FILE__) + '/em_http_request/em_http_request_0_x')
-
end
-
1
begin
-
1
require 'excon'
-
rescue LoadError
-
# excon not found
-
end
-
-
1
if defined?(Excon)
-
WebMock::VersionChecker.new('Excon', Excon::VERSION, '0.27.5').check_version!
-
-
module WebMock
-
module HttpLibAdapters
-
-
class ExconAdapter < HttpLibAdapter
-
PARAMS_TO_DELETE = [:expects, :idempotent,
-
:instrumentor_name, :instrumentor,
-
:response_block,
-
:__construction_args, :stack,
-
:connection, :response]
-
-
adapter_for :excon
-
-
def self.enable!
-
self.add_excon_stub
-
end
-
-
def self.disable!
-
self.remove_excon_stub
-
end
-
-
def self.add_excon_stub
-
if not @stub
-
@original_excon_mock_default = ::Excon.defaults[:mock]
-
::Excon.defaults[:mock] = true
-
@stub = ::Excon.stub({}) do |params|
-
self.handle_request(params)
-
end
-
end
-
end
-
-
def self.remove_excon_stub
-
::Excon.defaults[:mock] = @original_excon_mock_default
-
@original_excon_mock_default = nil
-
Excon.stubs.delete(@stub)
-
@stub = nil
-
end
-
-
def self.handle_request(params)
-
mock_request = self.build_request params.dup
-
WebMock::RequestRegistry.instance.requested_signatures.put(mock_request)
-
-
if mock_response = WebMock::StubRegistry.instance.response_for_request(mock_request)
-
self.perform_callbacks(mock_request, mock_response, :real_request => false)
-
response = self.real_response(mock_response)
-
response
-
elsif WebMock.net_connect_allowed?(mock_request.uri)
-
conn = new_excon_connection(params)
-
real_response = conn.request(request_params_from(params.merge(:mock => false)))
-
-
ExconAdapter.perform_callbacks(mock_request, ExconAdapter.mock_response(real_response), :real_request => true)
-
-
real_response.data
-
else
-
raise WebMock::NetConnectNotAllowedError.new(mock_request)
-
end
-
end
-
-
def self.new_excon_connection(params)
-
# Ensure the connection is constructed with the exact same args
-
# that the orginal connection was constructed with.
-
args = params.fetch(:__construction_args)
-
::Excon::Connection.new(connection_params_from args.merge(:mock => false))
-
end
-
-
def self.connection_params_from(hash)
-
hash = hash.dup
-
PARAMS_TO_DELETE.each { |key| hash.delete(key) }
-
hash
-
end
-
-
def self.request_params_from(hash)
-
hash = hash.dup
-
if defined?(Excon::VALID_REQUEST_KEYS)
-
hash.reject! {|key,_| !Excon::VALID_REQUEST_KEYS.include?(key) }
-
end
-
PARAMS_TO_DELETE.each { |key| hash.delete(key) }
-
hash
-
end
-
-
def self.to_query(hash)
-
string = ""
-
for key, values in hash
-
if values.nil?
-
string << key.to_s << '&'
-
else
-
for value in [*values]
-
string << key.to_s << '=' << CGI.escape(value.to_s) << '&'
-
end
-
end
-
end
-
string.chop! # remove trailing '&'
-
end
-
-
def self.build_request(params)
-
params = params.dup
-
method = (params.delete(:method) || :get).to_s.downcase.to_sym
-
params[:query] = to_query(params[:query]) if params[:query].is_a?(Hash)
-
uri = Addressable::URI.new(params).to_s
-
WebMock::RequestSignature.new method, uri, :body => body_from(params), :headers => params[:headers]
-
end
-
-
def self.body_from(params)
-
body = params[:body]
-
return body unless body.respond_to?(:read)
-
-
contents = body.read
-
body.rewind if body.respond_to?(:rewind)
-
contents
-
end
-
-
def self.real_response(mock)
-
raise Excon::Errors::Timeout if mock.should_timeout
-
mock.raise_error_if_any
-
{
-
:body => mock.body,
-
:status => mock.status[0].to_i,
-
:headers => mock.headers || {}
-
}
-
end
-
-
def self.mock_response(real)
-
mock = WebMock::Response.new
-
mock.status = real.status
-
mock.headers = real.headers
-
mock.body = real.body.dup
-
mock
-
end
-
-
def self.perform_callbacks(request, response, options = {})
-
return unless WebMock::CallbackRegistry.any_callbacks?
-
WebMock::CallbackRegistry.invoke_callbacks(options.merge(:lib => :excon), request, response)
-
end
-
end
-
end
-
end
-
-
Excon::Connection.class_eval do
-
def self.new(args)
-
args.delete(:__construction_args)
-
super(args).tap do |instance|
-
instance.data[:__construction_args] = args
-
end
-
end
-
end
-
end
-
1
module HTTP
-
1
class Client
-
1
alias_method :__perform__, :perform
-
-
1
def perform(request, options)
-
return __perform__(request, options) unless webmock_enabled?
-
WebMockPerform.new(request) { __perform__(request, options) }.exec
-
end
-
-
1
def webmock_enabled?
-
::WebMock::HttpLibAdapters::HttpGemAdapter.enabled?
-
end
-
end
-
end
-
1
module HTTP
-
1
class Request
-
1
def webmock_signature
-
::WebMock::RequestSignature.new(verb, uri.to_s, {
-
:headers => headers.to_h,
-
:body => body
-
})
-
end
-
end
-
end
-
1
module HTTP
-
1
class Response
-
1
def to_webmock
-
webmock_response = ::WebMock::Response.new
-
-
webmock_response.status = [status, reason]
-
webmock_response.body = body.to_s
-
webmock_response.headers = headers.to_h
-
-
webmock_response
-
end
-
-
1
def self.from_webmock(webmock_response, request_signature = nil)
-
status = webmock_response.status.first
-
headers = webmock_response.headers || {}
-
body = Body.new Streamer.new webmock_response.body
-
uri = URI request_signature.uri.to_s if request_signature
-
-
new(status, "1.1", headers, body, uri)
-
end
-
end
-
end
-
1
module HTTP
-
1
class Response
-
1
class Streamer
-
1
def initialize(str)
-
@io = StringIO.new str
-
end
-
-
1
def readpartial(size = HTTP::Client::BUFFER_SIZE)
-
@io.read size
-
end
-
end
-
end
-
end
-
1
module HTTP
-
1
class WebMockPerform
-
1
def initialize(request, &perform)
-
@request = request
-
@perform = perform
-
end
-
-
1
def exec
-
replay || perform || halt
-
end
-
-
1
def request_signature
-
unless @request_signature
-
@request_signature = @request.webmock_signature
-
register_request(@request_signature)
-
end
-
-
@request_signature
-
end
-
-
1
protected
-
-
1
def response_for_request(signature)
-
::WebMock::StubRegistry.instance.response_for_request(signature)
-
end
-
-
1
def register_request(signature)
-
::WebMock::RequestRegistry.instance.requested_signatures.put(signature)
-
end
-
-
1
def replay
-
webmock_response = response_for_request request_signature
-
-
return unless webmock_response
-
-
raise Errno::ETIMEDOUT if webmock_response.should_timeout
-
webmock_response.raise_error_if_any
-
-
invoke_callbacks(webmock_response, :real_request => false)
-
::HTTP::Response.from_webmock webmock_response, request_signature
-
end
-
-
1
def perform
-
return unless ::WebMock.net_connect_allowed?(request_signature.uri)
-
response = @perform.call
-
invoke_callbacks(response.to_webmock, :real_request => true)
-
response
-
end
-
-
1
def halt
-
raise ::WebMock::NetConnectNotAllowedError.new request_signature
-
end
-
-
1
def invoke_callbacks(webmock_response, options = {})
-
::WebMock::CallbackRegistry.invoke_callbacks(
-
options.merge({ :lib => :http_gem }),
-
request_signature,
-
webmock_response
-
)
-
end
-
end
-
end
-
1
begin
-
1
require "http"
-
1
__http_gem_found__ = true
-
rescue LoadError
-
__http_gem_found__ = false
-
end
-
-
1
if __http_gem_found__
-
1
WebMock::VersionChecker.new("HTTP Gem", HTTP::VERSION, "0.6.0").check_version!
-
-
1
module WebMock
-
1
module HttpLibAdapters
-
1
class HttpGemAdapter < HttpLibAdapter
-
1
adapter_for :http_gem
-
-
1
class << self
-
1
def enable!
-
1
@enabled = true
-
end
-
-
1
def disable!
-
1
@enabled = false
-
end
-
-
1
def enabled?
-
@enabled
-
end
-
end
-
end
-
end
-
end
-
-
1
require "webmock/http_lib_adapters/http_gem/client"
-
1
require "webmock/http_lib_adapters/http_gem/request"
-
1
require "webmock/http_lib_adapters/http_gem/response"
-
1
require "webmock/http_lib_adapters/http_gem/streamer"
-
1
require "webmock/http_lib_adapters/http_gem/webmock"
-
end
-
1
module WebMock
-
1
class HttpLibAdapter
-
1
def self.adapter_for(lib)
-
2
WebMock::HttpLibAdapterRegistry.instance.register(lib, self)
-
end
-
end
-
end
-
1
module WebMock
-
1
class HttpLibAdapterRegistry
-
1
include Singleton
-
-
1
attr_accessor :http_lib_adapters
-
-
1
def initialize
-
1
@http_lib_adapters = {}
-
end
-
-
1
def register(lib, adapter)
-
2
@http_lib_adapters[lib] = adapter
-
end
-
-
1
def each_adapter(&block)
-
1
@http_lib_adapters.each(&block)
-
end
-
end
-
end
-
1
begin
-
1
require 'httpclient'
-
rescue LoadError
-
# httpclient not found
-
end
-
-
1
if defined?(::HTTPClient)
-
-
module WebMock
-
module HttpLibAdapters
-
class HTTPClientAdapter < HttpLibAdapter
-
adapter_for :httpclient
-
-
OriginalHttpClient = ::HTTPClient unless const_defined?(:OriginalHttpClient)
-
-
def self.enable!
-
Object.send(:remove_const, :HTTPClient)
-
Object.send(:const_set, :HTTPClient, WebMockHTTPClient)
-
end
-
-
def self.disable!
-
Object.send(:remove_const, :HTTPClient)
-
Object.send(:const_set, :HTTPClient, OriginalHttpClient)
-
end
-
end
-
end
-
end
-
-
-
class WebMockHTTPClient < HTTPClient
-
alias_method :do_get_block_without_webmock, :do_get_block
-
alias_method :do_get_stream_without_webmock, :do_get_stream
-
-
def do_get_block(req, proxy, conn, &block)
-
do_get(req, proxy, conn, false, &block)
-
end
-
-
def do_get_stream(req, proxy, conn, &block)
-
do_get(req, proxy, conn, true, &block)
-
end
-
-
def do_get(req, proxy, conn, stream = false, &block)
-
request_signature = build_request_signature(req, :reuse_existing)
-
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_responses[request_signature]
-
webmock_response = webmock_responses.delete(request_signature)
-
response = build_httpclient_response(webmock_response, stream, req.header, &block)
-
@request_filter.each do |filter|
-
filter.filter_response(req, response)
-
end
-
res = conn.push(response)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :httpclient}, request_signature, webmock_response)
-
res
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
# in case there is a nil entry in the hash...
-
webmock_responses.delete(request_signature)
-
-
res = if stream
-
do_get_stream_without_webmock(req, proxy, conn, &block)
-
else
-
do_get_block_without_webmock(req, proxy, conn, &block)
-
end
-
res = conn.pop
-
conn.push(res)
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = build_webmock_response(res)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :httpclient, :real_request => true}, request_signature,
-
webmock_response)
-
end
-
res
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
def do_request_async(method, uri, query, body, extheader)
-
req = create_request(method, uri, query, body, extheader)
-
request_signature = build_request_signature(req)
-
webmock_request_signatures << request_signature
-
-
if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
-
super
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
def build_httpclient_response(webmock_response, stream = false, req_header = nil, &block)
-
body = stream ? StringIO.new(webmock_response.body) : webmock_response.body
-
response = HTTP::Message.new_response(body, req_header)
-
response.header.init_response(webmock_response.status[0])
-
response.reason=webmock_response.status[1]
-
webmock_response.headers.to_a.each { |name, value| response.header.set(name, value) }
-
-
raise HTTPClient::TimeoutError if webmock_response.should_timeout
-
webmock_response.raise_error_if_any
-
-
block.call(response, body) if block
-
-
response
-
end
-
end
-
-
def build_webmock_response(httpclient_response)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [httpclient_response.status, httpclient_response.reason]
-
-
webmock_response.headers = {}.tap do |hash|
-
httpclient_response.header.all.each do |(key, value)|
-
if hash.has_key?(key)
-
hash[key] = Array(hash[key]) + [value]
-
else
-
hash[key] = value
-
end
-
end
-
end
-
-
if httpclient_response.content.respond_to?(:read)
-
webmock_response.body = httpclient_response.content.read
-
body = HTTP::Message::Body.new
-
body.init_response(StringIO.new(webmock_response.body))
-
httpclient_response.body = body
-
else
-
webmock_response.body = httpclient_response.content
-
end
-
webmock_response
-
end
-
-
def build_request_signature(req, reuse_existing = false)
-
uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s)
-
uri.query = WebMock::Util::QueryMapper.values_to_query(req.header.request_query, :notation => WebMock::Config.instance.query_values_notation) if req.header.request_query
-
uri.port = req.header.request_uri.port
-
uri = uri.omit(:userinfo)
-
-
auth = www_auth.basic_auth
-
auth.challenge(req.header.request_uri, nil)
-
-
@request_filter.each do |filter|
-
filter.filter_request(req)
-
end
-
-
headers = req.header.all.inject({}) do |hdrs, header|
-
hdrs[header[0]] ||= []
-
hdrs[header[0]] << header[1]
-
hdrs
-
end
-
headers = headers_from_session(uri).merge(headers)
-
-
if (auth_cred = auth.get(req)) && auth.scheme == 'Basic'
-
userinfo = WebMock::Util::Headers.decode_userinfo_from_header(auth_cred)
-
userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo)
-
headers.reject! {|k,v| k =~ /[Aa]uthorization/ && v =~ /^Basic / } #we added it to url userinfo
-
uri.userinfo = userinfo
-
end
-
-
signature = WebMock::RequestSignature.new(
-
req.header.request_method.downcase.to_sym,
-
uri.to_s,
-
:body => req.http_body.dump,
-
:headers => headers
-
)
-
-
# reuse a previous identical signature object if we stored one for later use
-
if reuse_existing && previous_signature = previous_signature_for(signature)
-
return previous_signature
-
end
-
-
signature
-
end
-
-
def webmock_responses
-
@webmock_responses ||= Hash.new do |hash, request_signature|
-
hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
end
-
end
-
-
def webmock_request_signatures
-
@webmock_request_signatures ||= []
-
end
-
-
def previous_signature_for(signature)
-
return nil unless index = webmock_request_signatures.index(signature)
-
webmock_request_signatures.delete_at(index)
-
end
-
-
private
-
-
# some of the headers sent by HTTPClient are derived from
-
# the client session
-
def headers_from_session(uri)
-
session_headers = HTTP::Message::Headers.new
-
@session_manager.send(:open, uri).send(:set_header, MessageMock.new(session_headers))
-
session_headers.all.inject({}) do |hdrs, header|
-
hdrs[header[0]] = header[1]
-
hdrs
-
end
-
end
-
-
# Mocks a HTTPClient HTTP::Message
-
class MessageMock
-
attr_reader :header
-
-
def initialize(headers)
-
@header = headers
-
end
-
-
def http_version=(value);end
-
end
-
-
end
-
1
require 'net/http'
-
1
require 'net/https'
-
1
require 'stringio'
-
1
require File.join(File.dirname(__FILE__), 'net_http_response')
-
-
-
1
module WebMock
-
1
module HttpLibAdapters
-
1
class NetHttpAdapter < HttpLibAdapter
-
1
adapter_for :net_http
-
-
1
OriginalNetHTTP = Net::HTTP unless const_defined?(:OriginalNetHTTP)
-
1
OriginalNetBufferedIO = Net::BufferedIO unless const_defined?(:OriginalNetBufferedIO)
-
-
1
def self.enable!
-
1
Net.send(:remove_const, :BufferedIO)
-
1
Net.send(:remove_const, :HTTP)
-
1
Net.send(:remove_const, :HTTPSession)
-
1
Net.send(:const_set, :HTTP, @webMockNetHTTP)
-
1
Net.send(:const_set, :HTTPSession, @webMockNetHTTP)
-
1
Net.send(:const_set, :BufferedIO, Net::WebMockNetBufferedIO)
-
end
-
-
1
def self.disable!
-
1
Net.send(:remove_const, :BufferedIO)
-
1
Net.send(:remove_const, :HTTP)
-
1
Net.send(:remove_const, :HTTPSession)
-
1
Net.send(:const_set, :HTTP, OriginalNetHTTP)
-
1
Net.send(:const_set, :HTTPSession, OriginalNetHTTP)
-
1
Net.send(:const_set, :BufferedIO, OriginalNetBufferedIO)
-
-
#copy all constants from @webMockNetHTTP to original Net::HTTP
-
#in case any constants were added to @webMockNetHTTP instead of Net::HTTP
-
#after WebMock was enabled.
-
#i.e Net::HTTP::DigestAuth
-
1
@webMockNetHTTP.constants.each do |constant|
-
23
if !OriginalNetHTTP.constants.map(&:to_s).include?(constant.to_s)
-
OriginalNetHTTP.send(:const_set, constant, @webMockNetHTTP.const_get(constant))
-
end
-
end
-
end
-
-
1
@webMockNetHTTP = Class.new(Net::HTTP) do
-
1
class << self
-
1
def socket_type
-
15
StubSocket
-
end
-
-
1
if Module.method(:const_defined?).arity == 1
-
def const_defined?(name)
-
super || self.superclass.const_defined?(name)
-
end
-
else
-
1
def const_defined?(name, inherit=true)
-
super || self.superclass.const_defined?(name, inherit)
-
end
-
end
-
-
1
if Module.method(:const_get).arity != 1
-
1
def const_get(name, inherit=true)
-
super
-
rescue NameError
-
self.superclass.const_get(name, inherit)
-
end
-
end
-
-
1
if Module.method(:constants).arity != 0
-
1
def constants(inherit=true)
-
1
(super + self.superclass.constants(inherit)).uniq
-
end
-
end
-
end
-
-
1
def request(request, body = nil, &block)
-
15
request_signature = WebMock::NetHTTPUtility.request_signature_from_request(self, request, body)
-
-
15
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
15
if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
15
@socket = Net::HTTP.socket_type.new
-
15
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :net_http}, request_signature, webmock_response)
-
15
build_net_http_response(webmock_response, &block)
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
check_right_http_connection
-
after_request = lambda do |response|
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = build_webmock_response(response)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :net_http, :real_request => true}, request_signature, webmock_response)
-
end
-
response.extend Net::WebMockHTTPResponse
-
block.call response if block
-
response
-
end
-
super_with_after_request = lambda {
-
response = super(request, nil, &nil)
-
after_request.call(response)
-
}
-
if started?
-
if WebMock::Config.instance.net_http_connect_on_start
-
super_with_after_request.call
-
else
-
start_with_connect_without_finish {
-
super_with_after_request.call
-
}
-
end
-
else
-
start_with_connect {
-
super_with_after_request.call
-
}
-
end
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
-
1
def start_without_connect
-
raise IOError, 'HTTP session already opened' if @started
-
if block_given?
-
begin
-
@started = true
-
return yield(self)
-
ensure
-
do_finish
-
end
-
end
-
@started = true
-
self
-
end
-
-
-
1
def start_with_connect_without_finish # :yield: http
-
if block_given?
-
begin
-
do_start
-
return yield(self)
-
end
-
end
-
do_start
-
self
-
end
-
-
1
alias_method :start_with_connect, :start
-
-
1
def start(&block)
-
if WebMock::Config.instance.net_http_connect_on_start
-
super(&block)
-
else
-
start_without_connect(&block)
-
end
-
end
-
-
1
def build_net_http_response(webmock_response, &block)
-
15
response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1])
-
15
body = webmock_response.body
-
15
body = nil if body.to_s == ''
-
-
15
response.instance_variable_set(:@body, body)
-
15
webmock_response.headers.to_a.each do |name, values|
-
15
values = [values] unless values.is_a?(Array)
-
15
values.each do |value|
-
15
response.add_field(name, value)
-
end
-
end
-
-
15
response.instance_variable_set(:@read, true)
-
-
15
response.extend Net::WebMockHTTPResponse
-
-
15
raise Timeout::Error, "execution expired" if webmock_response.should_timeout
-
-
15
webmock_response.raise_error_if_any
-
-
15
yield response if block_given?
-
-
15
response
-
end
-
-
1
def build_webmock_response(net_http_response)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [
-
net_http_response.code.to_i,
-
net_http_response.message]
-
webmock_response.headers = net_http_response.to_hash
-
webmock_response.body = net_http_response.body
-
webmock_response
-
end
-
-
-
1
def check_right_http_connection
-
unless @@alredy_checked_for_right_http_connection ||= false
-
WebMock::NetHTTPUtility.puts_warning_for_right_http_if_needed
-
@@alredy_checked_for_right_http_connection = true
-
end
-
end
-
end
-
1
@webMockNetHTTP.version_1_2
-
[
-
[:Get, Net::HTTP::Get],
-
[:Post, Net::HTTP::Post],
-
[:Put, Net::HTTP::Put],
-
[:Delete, Net::HTTP::Delete],
-
[:Head, Net::HTTP::Head],
-
[:Options, Net::HTTP::Options]
-
1
].each do |c|
-
6
@webMockNetHTTP.const_set(c[0], c[1])
-
end
-
end
-
end
-
end
-
-
1
class StubSocket #:nodoc:
-
-
1
attr_accessor :read_timeout, :continue_timeout
-
-
1
def initialize(*args)
-
end
-
-
1
def closed?
-
@closed ||= true
-
end
-
-
1
def readuntil(*args)
-
end
-
-
end
-
-
1
module Net #:nodoc: all
-
-
1
class WebMockNetBufferedIO < BufferedIO
-
1
def initialize(io, debug_output = nil)
-
@read_timeout = 60
-
@rbuf = ''
-
@debug_output = debug_output
-
-
@io = case io
-
when Socket, OpenSSL::SSL::SSLSocket, IO, StringIO
-
io
-
when String
-
StringIO.new(io)
-
end
-
raise "Unable to create local socket" unless @io
-
end
-
end
-
-
end
-
-
-
1
module WebMock
-
1
module NetHTTPUtility
-
-
1
def self.request_signature_from_request(net_http, request, body = nil)
-
15
protocol = net_http.use_ssl? ? "https" : "http"
-
-
15
path = request.path
-
-
15
if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
-
path = path.request_uri
-
end
-
-
15
path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
-
-
15
if request["authorization"] =~ /^Basic /
-
userinfo = WebMock::Util::Headers.decode_userinfo_from_header(request["authorization"])
-
userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo) + "@"
-
else
-
15
userinfo = ""
-
end
-
-
15
uri = "#{protocol}://#{userinfo}#{net_http.address}:#{net_http.port}#{path}"
-
15
method = request.method.downcase.to_sym
-
-
105
headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
-
-
60
headers.reject! {|k,v| k =~ /[Aa]uthorization/ && v.first =~ /^Basic / } #we added it to url userinfo
-
-
-
15
if request.body_stream
-
body = request.body_stream.read
-
request.body_stream = nil
-
end
-
-
15
if body != nil && body.respond_to?(:read)
-
request.set_body_internal body.read
-
else
-
15
request.set_body_internal body
-
end
-
-
15
WebMock::RequestSignature.new(method, uri, :body => request.body, :headers => headers)
-
end
-
-
-
1
def self.check_right_http_connection
-
1
@was_right_http_connection_loaded = defined?(RightHttpConnection)
-
end
-
-
1
def self.puts_warning_for_right_http_if_needed
-
if !@was_right_http_connection_loaded && defined?(RightHttpConnection)
-
$stderr.puts "\nWarning: RightHttpConnection has to be required before WebMock is required !!!\n"
-
end
-
end
-
-
end
-
end
-
-
1
WebMock::NetHTTPUtility.check_right_http_connection
-
# This code is entierly copied from VCR (http://github.com/myronmarston/vcr) by courtesy of Myron Marston
-
-
# A Net::HTTP response that has already been read raises an IOError when #read_body
-
# is called with a destination string or block.
-
#
-
# This causes a problem when VCR records a response--it reads the body before yielding
-
# the response, and if the code that is consuming the HTTP requests uses #read_body, it
-
# can cause an error.
-
#
-
# This is a bit of a hack, but it allows a Net::HTTP response to be "re-read"
-
# after it has aleady been read. This attemps to preserve the behavior of
-
# #read_body, acting just as if it had never been read.
-
-
-
1
module Net
-
1
module WebMockHTTPResponse
-
1
def read_body(dest = nil, &block)
-
30
if !(defined?(@__read_body_previously_called).nil?) && @__read_body_previously_called
-
15
return super
-
end
-
15
return @body if dest.nil? && block.nil?
-
raise ArgumentError.new("both arg and block given for HTTP method") if dest && block
-
return nil if @body.nil?
-
-
dest ||= ::Net::ReadAdapter.new(block)
-
dest << @body
-
@body = dest
-
ensure
-
# allow subsequent calls to #read_body to proceed as normal, without our hack...
-
30
@__read_body_previously_called = true
-
end
-
end
-
end
-
-
1
begin
-
1
require 'patron'
-
rescue LoadError
-
# patron not found
-
end
-
-
1
if defined?(::Patron)
-
module WebMock
-
module HttpLibAdapters
-
class PatronAdapter < ::WebMock::HttpLibAdapter
-
adapter_for :patron
-
-
OriginalPatronSession = ::Patron::Session unless const_defined?(:OriginalPatronSession)
-
-
class WebMockPatronSession < ::Patron::Session
-
def handle_request(req)
-
request_signature =
-
WebMock::HttpLibAdapters::PatronAdapter.build_request_signature(req)
-
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
-
WebMock::HttpLibAdapters::PatronAdapter.
-
handle_file_name(req, webmock_response)
-
res = WebMock::HttpLibAdapters::PatronAdapter.
-
build_patron_response(webmock_response, default_response_charset)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :patron}, request_signature, webmock_response)
-
res
-
elsif WebMock.net_connect_allowed?(request_signature.uri)
-
res = super
-
if WebMock::CallbackRegistry.any_callbacks?
-
webmock_response = WebMock::HttpLibAdapters::PatronAdapter.
-
build_webmock_response(res)
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :patron, :real_request => true}, request_signature,
-
webmock_response)
-
end
-
res
-
else
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
end
-
-
def self.enable!
-
Patron.send(:remove_const, :Session)
-
Patron.send(:const_set, :Session, WebMockPatronSession)
-
end
-
-
def self.disable!
-
Patron.send(:remove_const, :Session)
-
Patron.send(:const_set, :Session, OriginalPatronSession)
-
end
-
-
def self.handle_file_name(req, webmock_response)
-
if req.action == :get && req.file_name
-
begin
-
File.open(req.file_name, "w") do |f|
-
f.write webmock_response.body
-
end
-
rescue Errno::EACCES
-
raise ArgumentError.new("Unable to open specified file.")
-
end
-
end
-
end
-
-
def self.build_request_signature(req)
-
uri = WebMock::Util::URI.heuristic_parse(req.url)
-
uri.path = uri.normalized_path.gsub("[^:]//","/")
-
uri.user = req.username
-
uri.password = req.password
-
-
if [:put, :post].include?(req.action)
-
if req.file_name
-
if !File.exist?(req.file_name) || !File.readable?(req.file_name)
-
raise ArgumentError.new("Unable to open specified file.")
-
end
-
request_body = File.read(req.file_name)
-
elsif req.upload_data
-
request_body = req.upload_data
-
else
-
raise ArgumentError.new("Must provide either data or a filename when doing a PUT or POST")
-
end
-
end
-
-
request_signature = WebMock::RequestSignature.new(
-
req.action,
-
uri.to_s,
-
:body => request_body,
-
:headers => req.headers
-
)
-
request_signature
-
end
-
-
def self.build_patron_response(webmock_response, default_response_charset)
-
raise ::Patron::TimeoutError if webmock_response.should_timeout
-
webmock_response.raise_error_if_any
-
-
header_fields = (webmock_response.headers || []).map { |(k, vs)| Array(vs).map { |v| "#{k}: #{v}" } }.flatten
-
status_line = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}"
-
header_data = ([status_line] + header_fields).join("\r\n")
-
-
::Patron::Response.new(
-
"",
-
webmock_response.status[0],
-
0,
-
header_data,
-
webmock_response.body,
-
default_response_charset
-
)
-
end
-
-
def self.build_webmock_response(patron_response)
-
webmock_response = WebMock::Response.new
-
reason = patron_response.status_line.
-
scan(%r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?))[0][2]
-
webmock_response.status = [patron_response.status, reason]
-
webmock_response.body = patron_response.body
-
webmock_response.headers = patron_response.headers
-
webmock_response
-
end
-
end
-
end
-
end
-
end
-
1
begin
-
1
require 'typhoeus'
-
rescue LoadError
-
# typhoeus not found
-
end
-
-
1
if defined?(Typhoeus)
-
WebMock::VersionChecker.new('Typhoeus', Typhoeus::VERSION, '0.3.2').check_version!
-
-
module WebMock
-
module HttpLibAdapters
-
class TyphoeusAdapter < HttpLibAdapter
-
adapter_for :typhoeus
-
-
def self.enable!
-
@disabled = false
-
add_before_callback
-
add_after_request_callback
-
::Typhoeus::Config.block_connection = true
-
end
-
-
def self.disable!
-
@disabled = true
-
remove_after_request_callback
-
remove_before_callback
-
::Typhoeus::Config.block_connection = false
-
end
-
-
def self.disabled?
-
!!@disabled
-
end
-
-
def self.add_before_callback
-
unless Typhoeus.before.include?(BEFORE_CALLBACK)
-
Typhoeus.before << BEFORE_CALLBACK
-
end
-
end
-
-
def self.remove_before_callback
-
Typhoeus.before.delete_if {|v| v == BEFORE_CALLBACK }
-
end
-
-
def self.add_after_request_callback
-
unless Typhoeus.on_complete.include?(AFTER_REQUEST_CALLBACK)
-
Typhoeus.on_complete << AFTER_REQUEST_CALLBACK
-
end
-
end
-
-
def self.remove_after_request_callback
-
Typhoeus.on_complete.delete_if {|v| v == AFTER_REQUEST_CALLBACK }
-
end
-
-
def self.build_request_signature(req)
-
uri = WebMock::Util::URI.heuristic_parse(req.url)
-
uri.path = uri.normalized_path.gsub("[^:]//","/")
-
if req.options[:userpwd]
-
uri.user, uri.password = req.options[:userpwd].split(':')
-
end
-
-
body = req.options[:body]
-
-
if body.is_a?(Hash)
-
body = WebMock::Util::QueryMapper.values_to_query(body)
-
end
-
-
request_signature = WebMock::RequestSignature.new(
-
req.options[:method] || :get,
-
uri.to_s,
-
:body => body,
-
:headers => req.options[:headers]
-
)
-
-
req.instance_variable_set(:@__webmock_request_signature, request_signature)
-
-
request_signature
-
end
-
-
def self.build_webmock_response(typhoeus_response)
-
webmock_response = WebMock::Response.new
-
webmock_response.status = [typhoeus_response.code, typhoeus_response.status_message]
-
webmock_response.body = typhoeus_response.body
-
webmock_response.headers = typhoeus_response.headers
-
webmock_response
-
end
-
-
def self.generate_typhoeus_response(request_signature, webmock_response)
-
response = if webmock_response.should_timeout
-
::Typhoeus::Response.new(
-
:code => 0,
-
:status_message => "",
-
:body => "",
-
:headers => {},
-
:return_code => :operation_timedout
-
)
-
else
-
::Typhoeus::Response.new(
-
:code => webmock_response.status[0],
-
:status_message => webmock_response.status[1],
-
:body => webmock_response.body,
-
:headers => webmock_response.headers
-
)
-
end
-
response.mock = :webmock
-
response
-
end
-
-
def self.request_hash(request_signature)
-
hash = {}
-
-
hash[:body] = request_signature.body
-
hash[:headers] = request_signature.headers
-
-
hash
-
end
-
-
AFTER_REQUEST_CALLBACK = Proc.new do |response|
-
request = response.request
-
request_signature = request.instance_variable_get(:@__webmock_request_signature)
-
webmock_response =
-
::WebMock::HttpLibAdapters::TyphoeusAdapter.
-
build_webmock_response(response)
-
if response.mock
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :typhoeus},
-
request_signature,
-
webmock_response
-
)
-
else
-
WebMock::CallbackRegistry.invoke_callbacks(
-
{:lib => :typhoeus, :real_request => true},
-
request_signature,
-
webmock_response
-
)
-
end
-
end
-
-
BEFORE_CALLBACK = Proc.new do |request|
-
Typhoeus::Expectation.all.delete_if {|e| e.from == :webmock }
-
res = true
-
-
unless WebMock::HttpLibAdapters::TyphoeusAdapter.disabled?
-
request_signature = ::WebMock::HttpLibAdapters::TyphoeusAdapter.build_request_signature(request)
-
request.block_connection = false;
-
-
::WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
-
-
if webmock_response = ::WebMock::StubRegistry.instance.response_for_request(request_signature)
-
# ::WebMock::HttpLibAdapters::TyphoeusAdapter.stub_typhoeus(request_signature, webmock_response, self)
-
response = ::WebMock::HttpLibAdapters::TyphoeusAdapter.generate_typhoeus_response(request_signature, webmock_response)
-
if request.respond_to?(:on_headers)
-
request.execute_headers_callbacks(response)
-
end
-
if request.respond_to?(:streaming?) && request.streaming?
-
response.options[:response_body] = ""
-
request.on_body.each { |callback| callback.call(webmock_response.body, response) }
-
end
-
request.finish(response)
-
webmock_response.raise_error_if_any
-
res = false
-
elsif !WebMock.net_connect_allowed?(request_signature.uri)
-
raise WebMock::NetConnectNotAllowedError.new(request_signature)
-
end
-
end
-
res
-
end
-
end
-
end
-
end
-
end
-
1
module WebMock
-
1
module Matchers
-
#this is a based on RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher
-
#https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
-
1
class HashIncludingMatcher
-
1
def initialize(expected)
-
@expected = Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(expected, :deep => true).sort]
-
end
-
-
1
def ==(actual)
-
@expected.all? {|k,v| actual.has_key?(k) && v == actual[k]}
-
rescue NoMethodError
-
false
-
end
-
-
1
def inspect
-
"hash_including(#{@expected.inspect})"
-
end
-
-
1
def self.from_rspec_matcher(matcher)
-
new(matcher.instance_variable_get(:@expected))
-
end
-
end
-
-
#this is a based on RSpec::Mocks::ArgumentMatchers::AnyArgMatcher
-
1
class AnyArgMatcher
-
1
def initialize(ignore)
-
end
-
-
1
def ==(other)
-
true
-
end
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class RackResponse < Response
-
1
def initialize(app)
-
@app = app
-
end
-
-
1
def evaluate(request)
-
env = build_rack_env(request)
-
-
status, headers, response = @app.call(env)
-
-
Response.new(
-
:body => body_from_rack_response(response),
-
:headers => headers,
-
:status => status
-
)
-
end
-
-
1
def body_from_rack_response(response)
-
body = ""
-
response.each { |line| body << line }
-
response.close if response.respond_to?(:close)
-
return body
-
end
-
-
1
def build_rack_env(request)
-
uri = request.uri
-
headers = (request.headers || {}).dup
-
body = request.body || ''
-
-
env = {
-
# CGI variables specified by Rack
-
'REQUEST_METHOD' => request.method.to_s.upcase,
-
'CONTENT_TYPE' => headers.delete('Content-Type'),
-
'CONTENT_LENGTH' => body.bytesize,
-
'PATH_INFO' => uri.path,
-
'QUERY_STRING' => uri.query || '',
-
'SERVER_NAME' => uri.host,
-
'SERVER_PORT' => uri.port,
-
'SCRIPT_NAME' => ""
-
}
-
-
env['HTTP_AUTHORIZATION'] = 'Basic ' + [uri.userinfo].pack('m').delete("\r\n") if uri.userinfo
-
-
# Rack-specific variables
-
env['rack.input'] = StringIO.new(body)
-
env['rack.errors'] = $stderr
-
env['rack.version'] = Rack::VERSION
-
env['rack.url_scheme'] = uri.scheme
-
env['rack.run_once'] = true
-
env['rack.session'] = session
-
env['rack.session.options'] = session_options
-
-
headers.each do |k, v|
-
env["HTTP_#{k.tr('-','_').upcase}"] = v
-
end
-
-
env
-
end
-
-
1
def session
-
@session ||= {}
-
end
-
-
1
def session_options
-
@session_options ||= {}
-
end
-
end
-
end
-
1
module WebMock
-
1
class RequestExecutionVerifier
-
-
1
attr_accessor :request_pattern, :expected_times_executed, :times_executed, :at_least_times_executed, :at_most_times_executed
-
-
1
def initialize(request_pattern = nil, expected_times_executed = nil, at_least_times_executed = nil, at_most_times_executed = nil)
-
3
@request_pattern = request_pattern
-
3
@expected_times_executed = expected_times_executed
-
3
@at_least_times_executed = at_least_times_executed
-
3
@at_most_times_executed = at_most_times_executed
-
end
-
-
1
def matches?
-
3
@times_executed =
-
RequestRegistry.instance.times_executed(@request_pattern)
-
-
3
if @at_least_times_executed
-
@times_executed >= @at_least_times_executed
-
3
elsif @at_most_times_executed
-
@times_executed <= @at_most_times_executed
-
else
-
3
@times_executed == (@expected_times_executed || 1)
-
end
-
end
-
-
1
def does_not_match?
-
@times_executed =
-
RequestRegistry.instance.times_executed(@request_pattern)
-
if @expected_times_executed
-
@times_executed != @expected_times_executed
-
else
-
@times_executed == 0
-
end
-
end
-
-
-
1
def failure_message
-
expected_times_executed = @expected_times_executed || 1
-
text = if @at_least_times_executed
-
%Q(The request #{request_pattern.to_s} was expected to execute at least #{times(@at_least_times_executed)} but it executed #{times(times_executed)})
-
elsif @at_most_times_executed
-
%Q(The request #{request_pattern.to_s} was expected to execute at most #{times(@at_most_times_executed)} but it executed #{times(times_executed)})
-
else
-
%Q(The request #{request_pattern.to_s} was expected to execute #{times(expected_times_executed)} but it executed #{times(times_executed)})
-
end
-
text << self.class.executed_requests_message
-
text
-
end
-
-
1
def failure_message_when_negated
-
text = if @at_least_times_executed
-
%Q(The request #{request_pattern.to_s} was not expected to execute at least #{times(@at_least_times_executed)} but it executed #{times(times_executed)})
-
elsif @at_most_times_executed
-
%Q(The request #{request_pattern.to_s} was not expected to execute at most #{times(@at_most_times_executed)} but it executed #{times(times_executed)})
-
elsif @expected_times_executed
-
%Q(The request #{request_pattern.to_s} was not expected to execute #{times(expected_times_executed)} but it executed #{times(times_executed)})
-
else
-
%Q(The request #{request_pattern.to_s} was expected to execute 0 times but it executed #{times(times_executed)})
-
end
-
text << self.class.executed_requests_message
-
text
-
end
-
-
1
def self.executed_requests_message
-
"\n\nThe following requests were made:\n\n#{RequestRegistry.instance.to_s}\n" + "="*60
-
end
-
-
1
private
-
-
1
def times(times)
-
"#{times} time#{ (times == 1) ? '' : 's'}"
-
end
-
-
end
-
end
-
1
module WebMock
-
-
1
module RSpecMatcherDetector
-
1
def rSpecHashIncludingMatcher?(matcher)
-
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashIncludingMatcher/
-
end
-
end
-
-
1
class RequestPattern
-
-
1
attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern
-
-
1
def initialize(method, uri, options = {})
-
18
@method_pattern = MethodPattern.new(method)
-
18
@uri_pattern = create_uri_pattern(uri)
-
18
@body_pattern = nil
-
18
@headers_pattern = nil
-
18
@with_block = nil
-
18
assign_options(options)
-
end
-
-
1
def with(options = {}, &block)
-
10
assign_options(options)
-
10
@with_block = block
-
10
self
-
end
-
-
1
def matches?(request_signature)
-
18
content_type = request_signature.headers['Content-Type'] if request_signature.headers
-
18
content_type = content_type.split(';').first if content_type
-
@method_pattern.matches?(request_signature.method) &&
-
18
@uri_pattern.matches?(request_signature.uri) &&
-
18
(@body_pattern.nil? || @body_pattern.matches?(request_signature.body, content_type || "")) &&
-
18
(@headers_pattern.nil? || @headers_pattern.matches?(request_signature.headers)) &&
-
18
(@with_block.nil? || @with_block.call(request_signature))
-
end
-
-
1
def to_s
-
string = "#{@method_pattern.to_s.upcase}"
-
string << " #{@uri_pattern.to_s}"
-
string << " with body #{@body_pattern.to_s}" if @body_pattern
-
string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
-
string << " with given block" if @with_block
-
string
-
end
-
-
1
private
-
-
-
1
def assign_options(options)
-
28
options = WebMock::Util::HashKeysStringifier.stringify_keys!(options, :deep => true)
-
28
@body_pattern = BodyPattern.new(options['body']) if options.has_key?('body')
-
28
@headers_pattern = HeadersPattern.new(options['headers']) if options.has_key?('headers')
-
28
@uri_pattern.add_query_params(options['query']) if options.has_key?('query')
-
end
-
-
1
def create_uri_pattern(uri)
-
18
if uri.is_a?(Regexp)
-
URIRegexpPattern.new(uri)
-
18
elsif uri.is_a?(Addressable::Template)
-
URIAddressablePattern.new(uri)
-
else
-
18
URIStringPattern.new(uri)
-
end
-
end
-
-
end
-
-
-
1
class MethodPattern
-
1
def initialize(pattern)
-
18
@pattern = pattern
-
end
-
-
1
def matches?(method)
-
18
@pattern == method || @pattern == :any
-
end
-
-
1
def to_s
-
@pattern.to_s
-
end
-
end
-
-
-
1
class URIPattern
-
1
include RSpecMatcherDetector
-
-
1
def initialize(pattern)
-
18
@pattern = case pattern
-
when Addressable::URI, Addressable::Template
-
pattern
-
else
-
18
WebMock::Util::URI.normalize_uri(pattern)
-
end
-
18
@query_params = nil
-
end
-
-
1
def add_query_params(query_params)
-
@query_params = if query_params.is_a?(Hash)
-
query_params
-
elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher)
-
query_params
-
elsif rSpecHashIncludingMatcher?(query_params)
-
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
-
else
-
WebMock::Util::QueryMapper.query_to_values(query_params, :notation => Config.instance.query_values_notation)
-
end
-
end
-
-
1
def to_s
-
str = @pattern.inspect
-
str += " with query params #{@query_params.inspect}" if @query_params
-
str
-
end
-
end
-
-
1
class URIRegexpPattern < URIPattern
-
1
def matches?(uri)
-
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } &&
-
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, :notation => Config.instance.query_values_notation))
-
end
-
-
1
def to_s
-
str = @pattern.inspect
-
str += " with query params #{@query_params.inspect}" if @query_params
-
str
-
end
-
end
-
-
1
class URIAddressablePattern < URIPattern
-
1
def matches?(uri)
-
if @query_params.nil?
-
# Let Addressable check the whole URI
-
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| @pattern.match(u) }
-
else
-
# WebMock checks the query, Addressable checks everything else
-
WebMock::Util::URI.variations_of_uri_as_strings(uri.omit(:query)).any? { |u| @pattern.match(u) } &&
-
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query))
-
end
-
end
-
-
1
def add_query_params(query_params)
-
warn "WebMock warning: ignoring query params in RFC 6570 template and checking them with WebMock"
-
super(query_params)
-
end
-
-
1
def to_s
-
str = @pattern.pattern.inspect
-
str += " with variables #{@pattern.variables.inspect}" if @pattern.variables
-
str
-
end
-
end
-
-
1
class URIStringPattern < URIPattern
-
1
def matches?(uri)
-
18
if @pattern.is_a?(Addressable::URI)
-
18
if @query_params
-
uri.omit(:query) === @pattern &&
-
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, :notation => Config.instance.query_values_notation))
-
else
-
18
uri === @pattern
-
end
-
else
-
false
-
end
-
end
-
-
1
def add_query_params(query_params)
-
super
-
if @query_params.is_a?(Hash) || @query_params.is_a?(String)
-
query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, :notation => Config.instance.query_values_notation) || {}).merge(@query_params)
-
@pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, :notation => WebMock::Config.instance.query_values_notation)
-
@query_params = nil
-
end
-
end
-
-
1
def to_s
-
str = WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
-
str += " with query params #{@query_params.inspect}" if @query_params
-
str
-
end
-
end
-
-
-
1
class BodyPattern
-
1
include RSpecMatcherDetector
-
-
1
BODY_FORMATS = {
-
'text/xml' => :xml,
-
'application/xml' => :xml,
-
'application/json' => :json,
-
'text/json' => :json,
-
'application/javascript' => :json,
-
'text/javascript' => :json,
-
'text/html' => :html,
-
'application/x-yaml' => :yaml,
-
'text/yaml' => :yaml,
-
'text/plain' => :plain
-
}
-
-
1
def initialize(pattern)
-
@pattern = if pattern.is_a?(Hash)
-
normalize_hash(pattern)
-
elsif rSpecHashIncludingMatcher?(pattern)
-
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(pattern)
-
else
-
pattern
-
end
-
end
-
-
1
def matches?(body, content_type = "")
-
if (@pattern).is_a?(Hash)
-
return true if @pattern.empty?
-
matching_hashes?(body_as_hash(body, content_type), @pattern)
-
elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher)
-
@pattern == body_as_hash(body, content_type)
-
else
-
empty_string?(@pattern) && empty_string?(body) ||
-
@pattern == body ||
-
@pattern === body
-
end
-
end
-
-
1
def to_s
-
@pattern.inspect
-
end
-
-
1
private
-
1
def body_as_hash(body, content_type)
-
case BODY_FORMATS[content_type]
-
when :json then
-
WebMock::Util::JSON.parse(body)
-
when :xml then
-
Crack::XML.parse(body)
-
else
-
WebMock::Util::QueryMapper.query_to_values(body, :notation => Config.instance.query_values_notation)
-
end
-
end
-
-
# Compare two hashes for equality
-
#
-
# For two hashes to match they must have the same length and all
-
# values must match when compared using `#===`.
-
#
-
# The following hashes are examples of matches:
-
#
-
# {a: /\d+/} and {a: '123'}
-
#
-
# {a: '123'} and {a: '123'}
-
#
-
# {a: {b: /\d+/}} and {a: {b: '123'}}
-
#
-
# {a: {b: 'wow'}} and {a: {b: 'wow'}}
-
#
-
# @param [Hash] query_parameters typically the result of parsing
-
# JSON, XML or URL encoded parameters.
-
#
-
# @param [Hash] pattern which contains keys with a string, hash or
-
# regular expression value to use for comparison.
-
#
-
# @return [Boolean] true if the paramaters match the comparison
-
# hash, false if not.
-
1
def matching_hashes?(query_parameters, pattern)
-
return false unless query_parameters.is_a?(Hash)
-
return false unless query_parameters.keys.sort == pattern.keys.sort
-
query_parameters.each do |key, actual|
-
expected = pattern[key]
-
-
if actual.is_a?(Hash) && expected.is_a?(Hash)
-
return false unless matching_hashes?(actual, expected)
-
else
-
return false unless expected === actual
-
end
-
end
-
true
-
end
-
-
1
def empty_string?(string)
-
string.nil? || string == ""
-
end
-
-
1
def normalize_hash(hash)
-
Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash, :deep => true).sort]
-
end
-
-
end
-
-
1
class HeadersPattern
-
1
def initialize(pattern)
-
@pattern = WebMock::Util::Headers.normalize_headers(pattern) || {}
-
end
-
-
1
def matches?(headers)
-
if empty_headers?(@pattern)
-
empty_headers?(headers)
-
else
-
return false if empty_headers?(headers)
-
@pattern.each do |key, value|
-
return false unless headers.has_key?(key) && value === headers[key]
-
end
-
true
-
end
-
end
-
-
1
def to_s
-
WebMock::Util::Headers.sorted_headers_string(@pattern)
-
end
-
-
1
private
-
-
1
def empty_headers?(headers)
-
headers.nil? || headers == {}
-
end
-
end
-
-
end
-
1
module WebMock
-
-
1
class RequestRegistry
-
1
include Singleton
-
-
1
attr_accessor :requested_signatures
-
-
1
def initialize
-
1
reset!
-
end
-
-
1
def reset!
-
64
self.requested_signatures = Util::HashCounter.new
-
end
-
-
1
def times_executed(request_pattern)
-
self.requested_signatures.hash.select { |request_signature, times_executed|
-
3
request_pattern.matches?(request_signature)
-
6
}.inject(0) {|sum, (_, times_executed)| sum + times_executed }
-
end
-
-
1
def to_s
-
if requested_signatures.hash.empty?
-
"No requests were made."
-
else
-
text = ""
-
self.requested_signatures.each do |request_signature, times_executed|
-
text << "#{request_signature} was made #{times_executed} time#{times_executed == 1 ? '' : 's' }\n"
-
end
-
text
-
end
-
end
-
-
end
-
end
-
1
module WebMock
-
-
1
class RequestSignature
-
-
1
attr_accessor :method, :uri, :body
-
1
attr_reader :headers
-
-
1
def initialize(method, uri, options = {})
-
15
self.method = method.to_sym
-
15
self.uri = uri.is_a?(Addressable::URI) ? uri : WebMock::Util::URI.normalize_uri(uri)
-
15
assign_options(options)
-
end
-
-
1
def to_s
-
33
string = "#{self.method.to_s.upcase}"
-
33
string << " #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}"
-
33
string << " with body '#{body.to_s}'" if body && body.to_s != ''
-
33
if headers && !headers.empty?
-
33
string << " with headers #{WebMock::Util::Headers.sorted_headers_string(headers)}"
-
end
-
33
string
-
end
-
-
1
def headers=(headers)
-
15
@headers = WebMock::Util::Headers.normalize_headers(headers)
-
end
-
-
1
def hash
-
33
self.to_s.hash
-
end
-
-
1
def eql?(other)
-
self.to_s == other.to_s
-
end
-
1
alias == eql?
-
-
1
def url_encoded?
-
headers && headers['Content-Type'] == 'application/x-www-form-urlencoded'
-
end
-
-
1
private
-
-
1
def assign_options(options)
-
15
self.body = options[:body] if options.has_key?(:body)
-
15
self.headers = options[:headers] if options.has_key?(:headers)
-
end
-
-
end
-
-
end
-
1
module WebMock
-
1
class RequestStub
-
-
1
attr_accessor :request_pattern
-
-
1
def initialize(method, uri)
-
15
@request_pattern = RequestPattern.new(method, uri)
-
15
@responses_sequences = []
-
15
self
-
end
-
-
1
def with(params = {}, &block)
-
10
@request_pattern.with(params, &block)
-
10
self
-
end
-
-
1
def to_return(*response_hashes, &block)
-
15
if block
-
@responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(block)])
-
else
-
30
@responses_sequences << ResponsesSequence.new([*response_hashes].flatten.map {|r| ResponseFactory.response_for(r)})
-
end
-
15
self
-
end
-
-
1
def to_rack(app, options={})
-
@responses_sequences << ResponsesSequence.new([RackResponse.new(app)])
-
end
-
-
1
def to_raise(*exceptions)
-
@responses_sequences << ResponsesSequence.new([*exceptions].flatten.map {|e|
-
ResponseFactory.response_for(:exception => e)
-
})
-
self
-
end
-
-
1
def to_timeout
-
@responses_sequences << ResponsesSequence.new([ResponseFactory.response_for(:should_timeout => true)])
-
self
-
end
-
-
1
def response
-
15
if @responses_sequences.empty?
-
WebMock::Response.new
-
15
elsif @responses_sequences.length > 1
-
@responses_sequences.shift if @responses_sequences.first.end?
-
@responses_sequences.first.next_response
-
else
-
15
@responses_sequences[0].next_response
-
end
-
end
-
-
1
def has_responses?
-
!@responses_sequences.empty?
-
end
-
-
1
def then
-
self
-
end
-
-
1
def times(number)
-
raise "times(N) accepts integers >= 1 only" if !number.is_a?(Fixnum) || number < 1
-
if @responses_sequences.empty?
-
raise "Invalid WebMock stub declaration." +
-
" times(N) can be declared only after response declaration."
-
end
-
@responses_sequences.last.times_to_repeat += number-1
-
self
-
end
-
-
1
def matches?(request_signature)
-
self.request_pattern.matches?(request_signature)
-
end
-
-
1
def to_s
-
self.request_pattern.to_s
-
end
-
-
1
def self.from_request_signature(signature)
-
stub = self.new(signature.method.to_sym, signature.uri.to_s)
-
-
if signature.body.to_s != ''
-
body = if signature.url_encoded?
-
WebMock::Util::QueryMapper.query_to_values(signature.body, :notation => Config.instance.query_values_notation)
-
else
-
signature.body
-
end
-
stub.with(:body => body)
-
end
-
-
if (signature.headers && !signature.headers.empty?)
-
stub.with(:headers => signature.headers)
-
end
-
stub
-
end
-
end
-
end
-
1
require "pathname"
-
-
#compatibility with Ruby 1.9.2 preview1 to allow reading raw responses
-
1
class StringIO
-
1
alias_method :read_nonblock, :sysread
-
end
-
-
1
module WebMock
-
-
1
class ResponseFactory
-
1
def self.response_for(options)
-
15
if options.respond_to?(:call)
-
WebMock::DynamicResponse.new(options)
-
else
-
15
WebMock::Response.new(options)
-
end
-
end
-
end
-
-
1
class Response
-
1
def initialize(options = {})
-
15
if options.is_a?(IO) || options.is_a?(String)
-
self.options = read_raw_response(options)
-
else
-
15
self.options = options
-
end
-
end
-
-
1
def headers
-
15
@headers
-
end
-
-
1
def headers=(headers)
-
15
@headers = headers
-
15
if @headers && !@headers.is_a?(Proc)
-
15
@headers = Util::Headers.normalize_headers(@headers)
-
end
-
end
-
-
1
def body
-
15
@body || ''
-
end
-
-
1
def body=(body)
-
15
@body = body
-
15
stringify_body!
-
end
-
-
1
def status
-
45
@status || [200, ""]
-
end
-
-
1
def status=(status)
-
15
@status = status.is_a?(Integer) ? [status, ""] : status
-
end
-
-
1
def exception
-
@exception
-
end
-
-
1
def exception=(exception)
-
15
@exception = case exception
-
when String then StandardError.new(exception)
-
when Class then exception.new('Exception from WebMock')
-
when Exception then exception
-
end
-
end
-
-
1
def raise_error_if_any
-
15
raise @exception if @exception
-
end
-
-
1
def should_timeout
-
15
@should_timeout == true
-
end
-
-
1
def options=(options)
-
15
self.headers = options[:headers]
-
15
self.status = options[:status]
-
15
self.body = options[:body]
-
15
self.exception = options[:exception]
-
15
@should_timeout = options[:should_timeout]
-
end
-
-
1
def evaluate(request_signature)
-
15
self.body = @body.call(request_signature) if @body.is_a?(Proc)
-
15
self.headers = @headers.call(request_signature) if @headers.is_a?(Proc)
-
15
self.status = @status.call(request_signature) if @status.is_a?(Proc)
-
15
@should_timeout = @should_timeout.call(request_signature) if @should_timeout.is_a?(Proc)
-
15
@exception = @exception.call(request_signature) if @exception.is_a?(Proc)
-
15
self
-
end
-
-
1
def ==(other)
-
self.body == other.body &&
-
self.headers === other.headers &&
-
self.status == other.status &&
-
self.exception == other.exception &&
-
self.should_timeout == other.should_timeout
-
end
-
-
1
private
-
-
1
def stringify_body!
-
15
if @body.is_a?(IO) || @body.is_a?(Pathname)
-
5
io = @body
-
5
@body = io.read
-
5
io.close if io.respond_to?(:close)
-
end
-
end
-
-
1
def read_raw_response(raw_response)
-
if raw_response.is_a?(IO)
-
string = raw_response.read
-
raw_response.close
-
raw_response = string
-
end
-
socket = ::Net::BufferedIO.new(raw_response)
-
response = ::Net::HTTPResponse.read_new(socket)
-
transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
-
response.reading_body(socket, true) {}
-
-
options = {}
-
options[:headers] = {}
-
response.each_header {|name, value| options[:headers][name] = value}
-
options[:headers]['transfer-encoding'] = transfer_encoding if transfer_encoding
-
options[:body] = response.read_body
-
options[:status] = [response.code.to_i, response.message]
-
options
-
end
-
-
end
-
-
1
class DynamicResponse < Response
-
1
attr_accessor :responder
-
-
1
def initialize(responder)
-
@responder = responder
-
end
-
-
1
def evaluate(request_signature)
-
options = @responder.call(request_signature)
-
Response.new(options)
-
end
-
end
-
end
-
1
module WebMock
-
-
1
class ResponsesSequence
-
-
1
attr_accessor :times_to_repeat
-
-
1
def initialize(responses)
-
15
@times_to_repeat = 1
-
15
@responses = responses
-
15
@current_position = 0
-
end
-
-
1
def end?
-
@times_to_repeat == 0
-
end
-
-
1
def next_response
-
15
if @times_to_repeat > 0
-
15
response = @responses[@current_position]
-
15
increase_position
-
15
response
-
else
-
@responses.last
-
end
-
end
-
-
1
private
-
-
1
def increase_position
-
15
if @current_position == (@responses.length - 1)
-
15
@current_position = 0
-
15
@times_to_repeat -= 1
-
else
-
@current_position += 1
-
end
-
end
-
-
end
-
-
end
-
1
require 'webmock'
-
-
# RSpec 1.x and 2.x compatibility
-
1
if defined?(RSpec::Expectations::ExpectationNotMetError)
-
RSPEC_NAMESPACE = RSPEC_CONFIGURER = RSpec
-
elsif defined?(Spec) && defined?(Spec.configure)
-
RSPEC_NAMESPACE = Spec
-
RSPEC_CONFIGURER = Spec::Runner
-
else
-
1
begin
-
1
require 'rspec/core'
-
1
require 'rspec/expectations'
-
1
RSPEC_NAMESPACE = RSPEC_CONFIGURER = RSpec
-
rescue LoadError
-
require 'spec'
-
RSPEC_NAMESPACE = Spec
-
RSPEC_CONFIGURER = Spec::Runner
-
end
-
end
-
-
1
require 'webmock/rspec/matchers'
-
-
1
RSPEC_CONFIGURER.configure { |config|
-
-
1
config.include WebMock::API
-
1
config.include WebMock::Matchers
-
-
1
config.after(:each) do
-
63
WebMock.reset!
-
end
-
}
-
-
1
WebMock::AssertionFailure.error_class = RSPEC_NAMESPACE::Expectations::ExpectationNotMetError
-
1
require 'webmock'
-
1
require 'webmock/rspec/matchers/request_pattern_matcher'
-
1
require 'webmock/rspec/matchers/webmock_matcher'
-
-
1
module WebMock
-
1
module Matchers
-
1
def have_been_made
-
3
WebMock::RequestPatternMatcher.new
-
end
-
-
1
def have_been_requested
-
WebMock::RequestPatternMatcher.new
-
end
-
-
-
1
def have_not_been_made
-
WebMock::RequestPatternMatcher.new.times(0)
-
end
-
-
1
def have_requested(method, uri)
-
WebMock::WebMockMatcher.new(method, uri)
-
end
-
-
1
def have_not_requested(method, uri)
-
WebMock::WebMockMatcher.new(method, uri).times(0)
-
end
-
end
-
end
-
1
module WebMock
-
1
class RequestPatternMatcher
-
-
1
def initialize
-
3
@request_execution_verifier = RequestExecutionVerifier.new
-
end
-
-
1
def once
-
@request_execution_verifier.expected_times_executed = 1
-
self
-
end
-
-
1
def twice
-
@request_execution_verifier.expected_times_executed = 2
-
self
-
end
-
-
1
def times(times)
-
@request_execution_verifier.expected_times_executed = times.to_i
-
self
-
end
-
-
1
def at_least_once
-
@request_execution_verifier.at_least_times_executed = 1
-
self
-
end
-
-
1
def at_least_twice
-
@request_execution_verifier.at_least_times_executed = 2
-
self
-
end
-
-
1
def at_least_times(times)
-
@request_execution_verifier.at_least_times_executed = times.to_i
-
self
-
end
-
-
1
def at_most_once
-
@request_execution_verifier.at_most_times_executed = 1
-
self
-
end
-
-
1
def at_most_twice
-
@request_execution_verifier.at_most_times_executed = 2
-
self
-
end
-
-
1
def at_most_times(times)
-
@request_execution_verifier.at_most_times_executed = times.to_i
-
self
-
end
-
-
1
def matches?(request_pattern)
-
3
@request_execution_verifier.request_pattern = request_pattern
-
3
@request_execution_verifier.matches?
-
end
-
-
1
def does_not_match?(request_pattern)
-
@request_execution_verifier.request_pattern = request_pattern
-
@request_execution_verifier.does_not_match?
-
end
-
-
1
def failure_message
-
@request_execution_verifier.failure_message
-
end
-
-
1
def failure_message_when_negated
-
@request_execution_verifier.failure_message_when_negated
-
end
-
-
# RSpec 2 compatibility:
-
1
alias_method :negative_failure_message, :failure_message_when_negated
-
end
-
end
-
1
module WebMock
-
1
class WebMockMatcher
-
-
1
def initialize(method, uri)
-
@request_execution_verifier = RequestExecutionVerifier.new
-
@request_execution_verifier.request_pattern = RequestPattern.new(method, uri)
-
end
-
-
1
def once
-
@request_execution_verifier.expected_times_executed = 1
-
self
-
end
-
-
1
def twice
-
@request_execution_verifier.expected_times_executed = 2
-
self
-
end
-
-
1
def with(options = {}, &block)
-
@request_execution_verifier.request_pattern.with(options, &block)
-
self
-
end
-
-
1
def times(times)
-
@request_execution_verifier.expected_times_executed = times.to_i
-
self
-
end
-
-
1
def matches?(webmock)
-
@request_execution_verifier.matches?
-
end
-
-
1
def does_not_match?(webmock)
-
@request_execution_verifier.does_not_match?
-
end
-
-
1
def failure_message
-
@request_execution_verifier.failure_message
-
end
-
-
1
def failure_message_when_negated
-
@request_execution_verifier.failure_message_when_negated
-
end
-
-
# RSpec 2 compatibility:
-
1
alias_method :negative_failure_message, :failure_message_when_negated
-
end
-
end
-
1
module WebMock
-
-
1
class StubRegistry
-
1
include Singleton
-
-
1
attr_accessor :request_stubs
-
-
1
def initialize
-
1
reset!
-
end
-
-
1
def global_stubs
-
15
@global_stubs ||= []
-
end
-
-
1
def reset!
-
64
self.request_stubs = []
-
end
-
-
1
def register_global_stub(&block)
-
# This hash contains the responses returned by the block,
-
# keyed by the exact request (using the object_id).
-
# That way, there's no race condition in case #to_return
-
# doesn't run immediately after stub.with.
-
responses = {}
-
-
stub = ::WebMock::RequestStub.new(:any, /.*/).with { |request|
-
responses[request.object_id] = block.call(request)
-
}.to_return(lambda { |request| responses.delete(request.object_id) })
-
-
global_stubs.push stub
-
end
-
-
1
def register_request_stub(stub)
-
15
request_stubs.insert(0, stub)
-
15
stub
-
end
-
-
1
def remove_request_stub(stub)
-
if not request_stubs.delete(stub)
-
raise "Request stub \n\n #{stub.to_s} \n\n is not registered."
-
end
-
end
-
-
1
def registered_request?(request_signature)
-
request_stub_for(request_signature)
-
end
-
-
1
def response_for_request(request_signature)
-
15
stub = request_stub_for(request_signature)
-
15
stub ? evaluate_response_for_request(stub.response, request_signature) : nil
-
end
-
-
1
private
-
-
1
def request_stub_for(request_signature)
-
15
(global_stubs + request_stubs).detect { |registered_request_stub|
-
15
registered_request_stub.request_pattern.matches?(request_signature)
-
}
-
end
-
-
1
def evaluate_response_for_request(response, request_signature)
-
15
response.dup.evaluate(request_signature)
-
end
-
-
end
-
end
-
1
module WebMock
-
1
class StubRequestSnippet
-
1
def initialize(request_stub)
-
@request_stub = request_stub
-
end
-
-
1
def to_s(with_response = true)
-
request_pattern = @request_stub.request_pattern
-
string = "stub_request(:#{request_pattern.method_pattern.to_s},"
-
string << " \"#{request_pattern.uri_pattern.to_s}\")"
-
-
with = ""
-
-
if (request_pattern.body_pattern)
-
with << ":body => #{request_pattern.body_pattern.to_s}"
-
end
-
-
if (request_pattern.headers_pattern)
-
with << ",\n " unless with.empty?
-
-
with << ":headers => #{request_pattern.headers_pattern.to_s}"
-
end
-
string << ".\n with(#{with})" unless with.empty?
-
if with_response
-
string << ".\n to_return(:status => 200, :body => \"\", :headers => {})"
-
end
-
string
-
end
-
end
-
end
-
1
require 'thread'
-
-
1
module WebMock
-
1
module Util
-
1
class Util::HashCounter
-
1
attr_accessor :hash
-
1
def initialize
-
64
self.hash = {}
-
64
@order = {}
-
64
@max = 0
-
64
@lock = ::Mutex.new
-
end
-
1
def put key, num=1
-
15
@lock.synchronize do
-
15
hash[key] = (hash[key] || 0) + num
-
15
@order[key] = @max = @max + 1
-
end
-
end
-
1
def get key
-
@lock.synchronize do
-
hash[key] || 0
-
end
-
end
-
-
1
def each(&block)
-
@order.to_a.sort {|a, b| a[1] <=> b[1]}.each do |a|
-
block.call(a[0], hash[a[0]])
-
end
-
end
-
end
-
end
-
end
-
1
module WebMock
-
1
module Util
-
1
class HashKeysStringifier
-
-
1
def self.stringify_keys!(arg, options = {})
-
28
case arg
-
when Array
-
arg.map { |elem|
-
options[:deep] ? stringify_keys!(elem, options) : elem
-
}
-
when Hash
-
28
Hash[
-
*arg.map { |key, value|
-
k = key.is_a?(Symbol) ? key.to_s : key
-
v = (options[:deep] ? stringify_keys!(value, options) : value)
-
[k,v]
-
}.inject([]) {|r,x| r + x}]
-
else
-
arg
-
end
-
end
-
-
end
-
end
-
end
-
1
module WebMock
-
-
1
module Util
-
-
1
class Headers
-
-
1
def self.normalize_headers(headers)
-
63
return nil unless headers
-
63
array = headers.map { |name, value|
-
429
[name.to_s.split(/_|-/).map { |segment| segment.capitalize }.join("-"),
-
case value
-
when Regexp then value
-
45
when Array then (value.size == 1) ? value.first : value.map {|v| v.to_s}.sort
-
114
else value.to_s
-
end
-
]
-
}
-
222
Hash[*array.inject([]) {|r,x| r + x}]
-
end
-
-
1
def self.sorted_headers_string(headers)
-
33
headers = WebMock::Util::Headers.normalize_headers(headers)
-
33
str = '{'
-
str << headers.map do |k,v|
-
99
v = case v
-
when Regexp then v.inspect
-
when Array then "["+v.map{|w| "'#{w.to_s}'"}.join(", ")+"]"
-
99
else "'#{v.to_s}'"
-
end
-
99
"'#{k}'=>#{v}"
-
33
end.sort.join(", ")
-
33
str << '}'
-
end
-
-
1
def self.decode_userinfo_from_header(header)
-
header.sub(/^Basic /, "").unpack("m").first
-
end
-
-
end
-
-
end
-
-
end
-
# This is a copy of https://github.com/jnunemaker/crack/blob/master/lib/crack/json.rb
-
# with date parsing removed
-
# Copyright (c) 2004-2008 David Heinemeier Hansson
-
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
1
module WebMock
-
1
module Util
-
1
class JSON
-
1
def self.parse(json)
-
YAML.load(unescape(convert_json_to_yaml(json)))
-
rescue ArgumentError
-
raise ParseError, "Invalid JSON string"
-
end
-
-
1
protected
-
1
def self.unescape(str)
-
str.gsub(/\\u([0-9a-f]{4})/) { [$1.hex].pack("U") }
-
end
-
-
# Ensure that ":" and "," are always followed by a space
-
1
def self.convert_json_to_yaml(json) #:nodoc:
-
scanner, quoting, marks, pos, times = StringScanner.new(json), false, [], nil, []
-
while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
-
case char = scanner[1]
-
when '"', "'"
-
if !quoting
-
quoting = char
-
pos = scanner.pos
-
elsif quoting == char
-
quoting = false
-
end
-
when ":",","
-
marks << scanner.pos - 1 unless quoting
-
when "\\"
-
scanner.skip(/\\/)
-
end
-
end
-
-
if marks.empty?
-
json.gsub(/\\\//, '/')
-
else
-
left_pos = [-1].push(*marks)
-
right_pos = marks << json.length
-
output = []
-
left_pos.each_with_index do |left, i|
-
output << json[left.succ..right_pos[i]]
-
end
-
output = output * " "
-
-
times.each { |i| output[i-1] = ' ' }
-
output.gsub!(/\\\//, '/')
-
output
-
end
-
end
-
end
-
end
-
end
-
1
module WebMock::Util
-
1
class QueryMapper
-
1
class << self
-
#This class is based on Addressable::URI pre 2.3.0
-
-
##
-
# Converts the query component to a Hash value.
-
#
-
# @option [Symbol] notation
-
# May be one of <code>:flat</code>, <code>:dot</code>, or
-
# <code>:subscript</code>. The <code>:dot</code> notation is not
-
# supported for assignment. Default value is <code>:subscript</code>.
-
#
-
# @return [Hash, Array] The query string parsed as a Hash or Array object.
-
#
-
# @example
-
# WebMock::Util::QueryMapper.query_to_values("?one=1&two=2&three=3")
-
# #=> {"one" => "1", "two" => "2", "three" => "3"}
-
# WebMock::Util::QueryMapper("?one[two][three]=four").query_values
-
# #=> {"one" => {"two" => {"three" => "four"}}}
-
# WebMock::Util::QueryMapper.query_to_values("?one.two.three=four",
-
# :notation => :dot
-
# )
-
# #=> {"one" => {"two" => {"three" => "four"}}}
-
# WebMock::Util::QueryMapper.query_to_values("?one[two][three]=four",
-
# :notation => :flat
-
# )
-
# #=> {"one[two][three]" => "four"}
-
# WebMock::Util::QueryMapper.query_to_values("?one.two.three=four",
-
# :notation => :flat
-
# )
-
# #=> {"one.two.three" => "four"}
-
# WebMock::Util::QueryMapper(
-
# "?one[two][three][]=four&one[two][three][]=five"
-
# )
-
# #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
-
# WebMock::Util::QueryMapper.query_to_values(
-
# "?one=two&one=three").query_values(:notation => :flat_array)
-
# #=> [['one', 'two'], ['one', 'three']]
-
1
def query_to_values(query, options={})
-
4
return nil if query.nil?
-
4
query.force_encoding('utf-8') if query.respond_to?(:force_encoding)
-
-
4
options[:notation] ||= :subscript
-
-
4
if ![:flat, :dot, :subscript, :flat_array].include?(options[:notation])
-
raise ArgumentError,
-
'Invalid notation. Must be one of: ' +
-
'[:flat, :dot, :subscript, :flat_array].'
-
end
-
-
4
empty_accumulator = :flat_array == options[:notation] ? [] : {}
-
-
4
query_array = collect_query_parts(query)
-
-
4
query_hash = collect_query_hash(query_array, empty_accumulator, options)
-
-
4
normalize_query_hash(query_hash, empty_accumulator, options)
-
end
-
-
1
def normalize_query_hash(query_hash, empty_accumulator, options)
-
4
query_hash.inject(empty_accumulator.dup) do |accumulator, (key, value)|
-
8
if options[:notation] == :flat_array
-
accumulator << [key, value]
-
else
-
8
accumulator[key] = value.kind_of?(Hash) ? dehash(value) : value
-
end
-
8
accumulator
-
end
-
end
-
-
1
def collect_query_parts(query)
-
4
query_parts = query.split('&').map do |pair|
-
8
pair.split('=', 2) if pair && !pair.empty?
-
end
-
4
query_parts.compact
-
end
-
-
1
def collect_query_hash(query_array, empty_accumulator, options)
-
4
query_array.compact.inject(empty_accumulator.dup) do |accumulator, (key, value)|
-
8
value = if value.nil?
-
true
-
else
-
8
::Addressable::URI.unencode_component(value.gsub(/\+/, ' '))
-
end
-
8
key = Addressable::URI.unencode_component(key)
-
8
key = key.dup.force_encoding(Encoding::ASCII_8BIT) if key.respond_to?(:force_encoding)
-
8
self.__send__("fill_accumulator_for_#{options[:notation]}", accumulator, key, value)
-
8
accumulator
-
end
-
end
-
-
1
def fill_accumulator_for_flat(accumulator, key, value)
-
if accumulator[key]
-
raise ArgumentError, "Key was repeated: #{key.inspect}"
-
end
-
accumulator[key] = value
-
end
-
-
1
def fill_accumulator_for_flat_array(accumulator, key, value)
-
accumulator << [key, value]
-
end
-
-
1
def fill_accumulator_for_dot(accumulator, key, value)
-
array_value = false
-
subkeys = key.split(".")
-
current_hash = accumulator
-
subkeys[0..-2].each do |subkey|
-
current_hash[subkey] = {} unless current_hash[subkey]
-
current_hash = current_hash[subkey]
-
end
-
if array_value
-
if current_hash[subkeys.last] && !current_hash[subkeys.last].is_a?(Array)
-
current_hash[subkeys.last] = [current_hash[subkeys.last]]
-
end
-
current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
-
current_hash[subkeys.last] << value
-
else
-
current_hash[subkeys.last] = value
-
end
-
end
-
-
1
def fill_accumulator_for_subscript(accumulator, key, value)
-
8
current_node = accumulator
-
8
subkeys = key.split(/(?=\[\w)/)
-
8
subkeys[0..-2].each do |subkey|
-
node = subkey =~ /\[\]\z/ ? [] : {}
-
subkey = subkey.gsub(/[\[\]]/, '')
-
if current_node.is_a? Array
-
container = current_node.find { |n| n.is_a?(Hash) && n.has_key?(subkey) }
-
if container
-
current_node = container[subkey]
-
else
-
current_node << {subkey => node}
-
current_node = node
-
end
-
else
-
current_node[subkey] = node unless current_node[subkey]
-
current_node = current_node[subkey]
-
end
-
end
-
8
last_key = subkeys.last
-
8
array_value = !!(last_key =~ /\[\]$/)
-
8
last_key = last_key.gsub(/[\[\]]/, '')
-
8
if current_node.is_a? Array
-
container = current_node.find { |n| n.is_a?(Hash) && n.has_key?(last_key) }
-
if container
-
if array_value
-
container[last_key] << value
-
else
-
container[last_key] = value
-
end
-
else
-
if array_value
-
current_node << {last_key => [value]}
-
else
-
current_node << {last_key => value}
-
end
-
end
-
else
-
8
if array_value
-
current_node[last_key] = [] unless current_node[last_key]
-
current_node[last_key] << value
-
else
-
8
current_node[last_key] = value
-
end
-
end
-
end
-
-
##
-
# Sets the query component for this URI from a Hash object.
-
# This method produces a query string using the :subscript notation.
-
# An empty Hash will result in a nil query.
-
#
-
# @param [Hash, #to_hash, Array] new_query_values The new query values.
-
1
def values_to_query(new_query_values, options = {})
-
4
options[:notation] ||= :subscript
-
4
return if new_query_values.nil?
-
-
4
unless new_query_values.is_a?(Array)
-
4
unless new_query_values.respond_to?(:to_hash)
-
raise TypeError,
-
"Can't convert #{new_query_values.class} into Hash."
-
end
-
4
new_query_values = new_query_values.to_hash
-
4
new_query_values = new_query_values.inject([]) do |object, (key, value)|
-
8
key = key.to_s if key.is_a?(::Symbol) || key.nil?
-
8
if value.is_a?(Array)
-
value.each { |v| object << [key.to_s + '[]', v] }
-
elsif value.is_a?(Hash)
-
value.each { |k, v| object << ["#{key.to_s}[#{k}]", v]}
-
else
-
8
object << [key.to_s, value]
-
end
-
8
object
-
end
-
# Useful default for OAuth and caching.
-
# Only to be used for non-Array inputs. Arrays should preserve order.
-
4
new_query_values.sort!
-
end
-
-
4
buffer = ''
-
4
new_query_values.each do |parent, value|
-
8
encoded_parent = ::Addressable::URI.encode_component(
-
parent.dup, ::Addressable::URI::CharacterClasses::UNRESERVED
-
)
-
8
buffer << "#{to_query(encoded_parent, value, options)}&"
-
end
-
4
buffer.chop
-
end
-
-
1
def dehash(hash)
-
hash.each do |(key, value)|
-
if value.is_a?(::Hash)
-
hash[key] = self.dehash(value)
-
end
-
end
-
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
-
hash.sort.inject([]) do |accu, (_, value)|
-
accu << value; accu
-
end
-
else
-
hash
-
end
-
end
-
-
##
-
# Joins and converts parent and value into a properly encoded and
-
# ordered URL query.
-
#
-
# @private
-
# @param [String] parent an URI encoded component.
-
# @param [Array, Hash, Symbol, #to_str] value
-
#
-
# @return [String] a properly escaped and ordered URL query.
-
-
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
-
1
def to_query(parent, value, options = {})
-
8
options[:notation] ||= :subscript
-
8
case value
-
when ::Hash
-
value = value.map do |key, val|
-
[
-
::Addressable::URI.encode_component(key.dup, ::Addressable::URI::CharacterClasses::UNRESERVED),
-
val
-
]
-
end
-
value.sort!
-
buffer = ''
-
value.each do |key, val|
-
new_parent = options[:notation] != :flat_array ? "#{parent}[#{key}]" : parent
-
buffer << "#{to_query(new_parent, val, options)}&"
-
end
-
buffer.chop
-
when ::Array
-
buffer = ''
-
value.each_with_index do |val, i|
-
new_parent = options[:notation] != :flat_array ? "#{parent}[#{i}]" : parent
-
buffer << "#{to_query(new_parent, val, options)}&"
-
end
-
buffer.chop
-
when TrueClass
-
parent
-
else
-
8
encoded_value = Addressable::URI.encode_component(
-
value.to_s.dup, Addressable::URI::CharacterClasses::UNRESERVED
-
)
-
8
"#{parent}=#{encoded_value}"
-
end
-
end
-
end
-
-
end
-
end
-
1
module WebMock
-
-
1
module Util
-
-
1
class URI
-
1
module CharacterClasses
-
1
USERINFO = Addressable::URI::CharacterClasses::UNRESERVED + Addressable::URI::CharacterClasses::SUB_DELIMS + "\\:"
-
end
-
-
1
ADDRESSABLE_URIS = Hash.new do |hash, key|
-
4
hash[key] = Addressable::URI.heuristic_parse(key)
-
end
-
-
1
NORMALIZED_URIS = Hash.new do |hash, uri|
-
4
normalized_uri = WebMock::Util::URI.heuristic_parse(uri)
-
4
if normalized_uri.query_values
-
4
sorted_query_values = sort_query_values(WebMock::Util::QueryMapper.query_to_values(normalized_uri.query, :notation => Config.instance.query_values_notation) || {})
-
4
normalized_uri.query = WebMock::Util::QueryMapper.values_to_query(sorted_query_values, :notation => WebMock::Config.instance.query_values_notation)
-
end
-
4
normalized_uri = normalized_uri.normalize #normalize! is slower
-
4
normalized_uri.query = normalized_uri.query.gsub("+", "%2B") if normalized_uri.query
-
4
normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
-
4
hash[uri] = normalized_uri
-
end
-
-
1
def self.heuristic_parse(uri)
-
4
ADDRESSABLE_URIS[uri].dup
-
end
-
-
1
def self.normalize_uri(uri)
-
33
return uri if uri.is_a?(Regexp)
-
33
uri = 'http://' + uri unless uri.match('^https?://') if uri.is_a?(String)
-
33
NORMALIZED_URIS[uri].dup
-
end
-
-
1
def self.variations_of_uri_as_strings(uri_object)
-
normalized_uri = normalize_uri(uri_object.dup).freeze
-
uris = [ normalized_uri ]
-
-
if normalized_uri.path == '/'
-
uris = uris_with_trailing_slash_and_without(uris)
-
end
-
-
uris = uris_encoded_and_unencoded(uris)
-
-
if normalized_uri.port == Addressable::URI.port_mapping[normalized_uri.scheme]
-
uris = uris_with_inferred_port_and_without(uris)
-
end
-
-
if normalized_uri.scheme == "http"
-
uris = uris_with_scheme_and_without(uris)
-
end
-
-
uris.map {|uri| uri.to_s.gsub(/^\/\//,'') }.uniq
-
end
-
-
1
def self.strip_default_port_from_uri_string(uri_string)
-
33
case uri_string
-
when %r{^http://} then uri_string.sub(%r{:80(/|$)}, '\1')
-
33
when %r{^https://} then uri_string.sub(%r{:443(/|$)}, '\1')
-
else uri_string
-
end
-
end
-
-
1
def self.encode_unsafe_chars_in_userinfo(userinfo)
-
Addressable::URI.encode_component(userinfo, WebMock::Util::URI::CharacterClasses::USERINFO)
-
end
-
-
1
def self.is_uri_localhost?(uri)
-
uri.is_a?(Addressable::URI) &&
-
%w(localhost 127.0.0.1 0.0.0.0).include?(uri.host)
-
end
-
-
1
private
-
-
1
def self.sort_query_values(query_values)
-
4
sorted_query_values = query_values.sort
-
12
query_values.is_a?(Hash) ? Hash[*sorted_query_values.inject([]) { |values, pair| values + pair}] : sorted_query_values
-
end
-
-
1
def self.uris_with_inferred_port_and_without(uris)
-
uris.map { |uri|
-
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
-
[ uri, uri.gsub(%r{(:80)|(:443)}, "").freeze ]
-
}.flatten
-
end
-
-
1
def self.uris_encoded_and_unencoded(uris)
-
uris.map do |uri|
-
[ uri.to_s, Addressable::URI.unencode(uri, String).freeze ]
-
end.flatten
-
end
-
-
1
def self.uris_with_scheme_and_without(uris)
-
uris.map { |uri|
-
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
-
[ uri, uri.gsub(%r{^https?://},"").freeze ]
-
}.flatten
-
end
-
-
1
def self.uris_with_trailing_slash_and_without(uris)
-
uris = uris.map { |uri|
-
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
-
[ uri, uri.omit(:path).freeze ]
-
}.flatten
-
end
-
-
end
-
end
-
-
end
-
# This code was created based on https://github.com/myronmarston/vcr/blob/master/lib/vcr/util/version_checker.rb
-
# Thanks to @myronmarston
-
-
# Copyright (c) 2010-2012 Myron Marston
-
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
1
module WebMock
-
1
class VersionChecker
-
1
def initialize(library_name, library_version, min_patch_level, max_minor_version = nil)
-
1
@library_name, @library_version = library_name, library_version
-
1
@min_patch_level, @max_minor_version = min_patch_level, max_minor_version
-
-
1
@major, @minor, @patch = parse_version(library_version)
-
1
@min_major, @min_minor, @min_patch = parse_version(min_patch_level)
-
1
@max_major, @max_minor = parse_version(max_minor_version) if max_minor_version
-
-
1
@comparison_result = compare_version
-
end
-
-
1
def check_version!
-
1
warn_about_too_low if too_low?
-
1
warn_about_too_high if too_high?
-
end
-
-
1
private
-
-
1
def too_low?
-
1
@comparison_result == :too_low
-
end
-
-
1
def too_high?
-
1
@comparison_result == :too_high
-
end
-
-
1
def warn_about_too_low
-
warn_in_red "You are using #{@library_name} #{@library_version}. " +
-
"WebMock supports version #{version_requirement}."
-
end
-
-
1
def warn_about_too_high
-
warn_in_red "You are using #{@library_name} #{@library_version}. " +
-
"WebMock is known to work with #{@library_name} #{version_requirement}. " +
-
"It may not work with this version."
-
end
-
-
1
def warn_in_red(text)
-
Kernel.warn colorize(text, "\e[31m")
-
end
-
-
1
def compare_version
-
case
-
when @major < @min_major then :too_low
-
when @max_major && @major > @max_major then :too_high
-
when @major > @min_major then :ok
-
when @minor < @min_minor then :too_low
-
when @max_minor && @minor > @max_minor then :too_high
-
when @minor > @min_minor then :ok
-
when @patch < @min_patch then :too_low
-
1
end
-
end
-
-
1
def version_requirement
-
req = ">= #{@min_patch_level}"
-
req += ", < #{@max_major}.#{@max_minor + 1}" if @max_minor
-
req
-
end
-
-
1
def parse_version(version)
-
8
version.split('.').map { |v| v.to_i }
-
end
-
-
1
def colorize(text, color_code)
-
"#{color_code}#{text}\e[0m"
-
end
-
end
-
end
-
1
module WebMock
-
1
VERSION = '1.20.0' unless defined?(::WebMock::VERSION)
-
end
-
1
module WebMock
-
-
1
def self.included(clazz)
-
WebMock::Deprecation.warning("include WebMock is deprecated. Please include WebMock::API instead")
-
if clazz.instance_methods.map(&:to_s).include?('request')
-
warn "WebMock#request was not included in #{clazz} to avoid name collision"
-
else
-
clazz.class_eval do
-
def request(method, uri)
-
WebMock::Deprecation.warning("WebMock#request is deprecated. Please use WebMock::API#a_request method instead")
-
WebMock.a_request(method, uri)
-
end
-
end
-
end
-
end
-
-
1
include WebMock::API
-
1
extend WebMock::API
-
-
1
class << self
-
1
alias :request :a_request
-
end
-
-
1
def self.version
-
VERSION
-
end
-
-
1
def self.disable!(options = {})
-
except = [options[:except]].flatten.compact
-
HttpLibAdapterRegistry.instance.each_adapter do |name, adapter|
-
adapter.enable!
-
adapter.disable! unless except.include?(name)
-
end
-
end
-
-
1
def self.enable!(options = {})
-
1
except = [options[:except]].flatten.compact
-
1
HttpLibAdapterRegistry.instance.each_adapter do |name, adapter|
-
2
adapter.disable!
-
2
adapter.enable! unless except.include?(name)
-
end
-
end
-
-
1
def self.allow_net_connect!(options = {})
-
Config.instance.allow_net_connect = true
-
Config.instance.net_http_connect_on_start = options[:net_http_connect_on_start]
-
end
-
-
1
def self.disable_net_connect!(options = {})
-
1
Config.instance.allow_net_connect = false
-
1
Config.instance.allow_localhost = options[:allow_localhost]
-
1
Config.instance.allow = options[:allow]
-
1
Config.instance.net_http_connect_on_start = options[:net_http_connect_on_start]
-
end
-
-
1
def self.net_connect_allowed?(uri = nil)
-
if uri.is_a?(String)
-
uri = WebMock::Util::URI.normalize_uri(uri)
-
end
-
-
Config.instance.allow_net_connect ||
-
( Config.instance.allow_localhost && WebMock::Util::URI.is_uri_localhost?(uri) ||
-
Config.instance.allow && net_connect_explicit_allowed?(Config.instance.allow, uri) )
-
end
-
-
1
def self.net_connect_explicit_allowed?(allowed, uri=nil)
-
case allowed
-
when Array
-
allowed.any? { |allowed_item| net_connect_explicit_allowed?(allowed_item, uri) }
-
when Regexp
-
uri.to_s =~ allowed
-
when String
-
allowed == uri.host ||
-
allowed == "#{uri.host}:#{uri.port}"
-
end
-
end
-
-
1
def self.hide_stubbing_instructions!
-
Config.instance.show_stubbing_instructions = false
-
end
-
-
1
def self.show_stubbing_instructions!
-
Config.instance.show_stubbing_instructions = true
-
end
-
-
1
def self.show_stubbing_instructions?
-
Config.instance.show_stubbing_instructions
-
end
-
-
1
def self.reset!
-
63
WebMock::RequestRegistry.instance.reset!
-
63
WebMock::StubRegistry.instance.reset!
-
end
-
-
1
def self.reset_webmock
-
WebMock::Deprecation.warning("WebMock.reset_webmock is deprecated. Please use WebMock.reset! method instead")
-
reset!
-
end
-
-
1
def self.reset_callbacks
-
WebMock::CallbackRegistry.reset
-
end
-
-
1
def self.after_request(options={}, &block)
-
WebMock::CallbackRegistry.add_callback(options, block)
-
end
-
-
1
def self.registered_request?(request_signature)
-
WebMock::StubRegistry.instance.registered_request?(request_signature)
-
end
-
-
1
def self.print_executed_requests
-
puts WebMock::RequestExecutionVerifier.executed_requests_message
-
end
-
-
1
def self.globally_stub_request(&block)
-
WebMock::StubRegistry.instance.register_global_stub(&block)
-
end
-
-
%w(
-
allow_net_connect!
-
disable_net_connect!
-
net_connect_allowed?
-
reset_webmock
-
reset_callbacks
-
after_request
-
registered_request?
-
1
).each do |method|
-
7
self.class_eval(%Q(
-
def #{method}(*args, &block)
-
WebMock::Deprecation.warning("WebMock##{method} instance method is deprecated. Please use WebMock.#{method} class method instead")
-
WebMock.#{method}(*args, &block)
-
end
-
))
-
end
-
-
1
self.enable!
-
end